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/collector.py
CHANGED
|
@@ -9,13 +9,11 @@ import contextlib
|
|
|
9
9
|
import functools
|
|
10
10
|
import os
|
|
11
11
|
import sys
|
|
12
|
-
|
|
13
12
|
from collections.abc import Mapping
|
|
14
13
|
from types import FrameType
|
|
15
|
-
from typing import
|
|
14
|
+
from typing import Any, Callable, TypeVar, cast
|
|
16
15
|
|
|
17
16
|
from coverage import env
|
|
18
|
-
from coverage.config import CoverageConfig
|
|
19
17
|
from coverage.core import Core
|
|
20
18
|
from coverage.data import CoverageData
|
|
21
19
|
from coverage.debug import short_stack
|
|
@@ -26,11 +24,11 @@ from coverage.types import (
|
|
|
26
24
|
TArc,
|
|
27
25
|
TCheckIncludeFn,
|
|
28
26
|
TFileDisposition,
|
|
27
|
+
Tracer,
|
|
29
28
|
TShouldStartContextFn,
|
|
30
29
|
TShouldTraceFn,
|
|
31
30
|
TTraceData,
|
|
32
31
|
TTraceFn,
|
|
33
|
-
Tracer,
|
|
34
32
|
TWarnFn,
|
|
35
33
|
)
|
|
36
34
|
|
|
@@ -61,9 +59,6 @@ class Collector:
|
|
|
61
59
|
# the top, and resumed when they become the top again.
|
|
62
60
|
_collectors: list[Collector] = []
|
|
63
61
|
|
|
64
|
-
# The concurrency settings we support here.
|
|
65
|
-
LIGHT_THREADS = {"greenlet", "eventlet", "gevent"}
|
|
66
|
-
|
|
67
62
|
def __init__(
|
|
68
63
|
self,
|
|
69
64
|
core: Core,
|
|
@@ -113,8 +108,7 @@ class Collector:
|
|
|
113
108
|
self.file_mapper = file_mapper
|
|
114
109
|
self.branch = branch
|
|
115
110
|
self.warn = warn
|
|
116
|
-
|
|
117
|
-
assert isinstance(self.concurrency, list), f"Expected a list: {self.concurrency!r}"
|
|
111
|
+
assert isinstance(concurrency, list), f"Expected a list: {concurrency!r}"
|
|
118
112
|
|
|
119
113
|
self.pid = os.getpid()
|
|
120
114
|
|
|
@@ -126,34 +120,27 @@ class Collector:
|
|
|
126
120
|
|
|
127
121
|
self.concur_id_func = None
|
|
128
122
|
|
|
129
|
-
# We can handle a few concurrency options here, but only one at a time.
|
|
130
|
-
concurrencies = set(self.concurrency)
|
|
131
|
-
unknown = concurrencies - CoverageConfig.CONCURRENCY_CHOICES
|
|
132
|
-
if unknown:
|
|
133
|
-
show = ", ".join(sorted(unknown))
|
|
134
|
-
raise ConfigError(f"Unknown concurrency choices: {show}")
|
|
135
|
-
light_threads = concurrencies & self.LIGHT_THREADS
|
|
136
|
-
if len(light_threads) > 1:
|
|
137
|
-
show = ", ".join(sorted(light_threads))
|
|
138
|
-
raise ConfigError(f"Conflicting concurrency settings: {show}")
|
|
139
123
|
do_threading = False
|
|
140
124
|
|
|
141
125
|
tried = "nothing" # to satisfy pylint
|
|
142
126
|
try:
|
|
143
|
-
if "greenlet" in
|
|
127
|
+
if "greenlet" in concurrency:
|
|
144
128
|
tried = "greenlet"
|
|
145
129
|
import greenlet
|
|
130
|
+
|
|
146
131
|
self.concur_id_func = greenlet.getcurrent
|
|
147
|
-
elif "eventlet" in
|
|
132
|
+
elif "eventlet" in concurrency:
|
|
148
133
|
tried = "eventlet"
|
|
149
|
-
import eventlet.greenthread
|
|
134
|
+
import eventlet.greenthread
|
|
135
|
+
|
|
150
136
|
self.concur_id_func = eventlet.greenthread.getcurrent
|
|
151
|
-
elif "gevent" in
|
|
137
|
+
elif "gevent" in concurrency:
|
|
152
138
|
tried = "gevent"
|
|
153
|
-
import gevent
|
|
139
|
+
import gevent
|
|
140
|
+
|
|
154
141
|
self.concur_id_func = gevent.getcurrent
|
|
155
142
|
|
|
156
|
-
if "thread" in
|
|
143
|
+
if "thread" in concurrency:
|
|
157
144
|
do_threading = True
|
|
158
145
|
except ImportError as ex:
|
|
159
146
|
msg = f"Couldn't trace with concurrency={tried}, the module isn't installed."
|
|
@@ -162,15 +149,17 @@ class Collector:
|
|
|
162
149
|
if self.concur_id_func and not hasattr(core.tracer_class, "concur_id_func"):
|
|
163
150
|
raise ConfigError(
|
|
164
151
|
"Can't support concurrency={} with {}, only threads are supported.".format(
|
|
165
|
-
tried,
|
|
152
|
+
tried,
|
|
153
|
+
self.tracer_name(),
|
|
166
154
|
),
|
|
167
155
|
)
|
|
168
156
|
|
|
169
|
-
if do_threading or not
|
|
157
|
+
if do_threading or not concurrency:
|
|
170
158
|
# It's important to import threading only if we need it. If
|
|
171
159
|
# it's imported early, and the program being measured uses
|
|
172
160
|
# gevent, then gevent's monkey-patching won't work properly.
|
|
173
161
|
import threading
|
|
162
|
+
|
|
174
163
|
self.threading = threading
|
|
175
164
|
|
|
176
165
|
self.reset()
|
|
@@ -222,7 +211,8 @@ class Collector:
|
|
|
222
211
|
# being excluded by the inclusion rules, in which case the
|
|
223
212
|
# FileDisposition will be replaced by None in the cache.
|
|
224
213
|
if env.PYPY:
|
|
225
|
-
import __pypy__
|
|
214
|
+
import __pypy__ # pylint: disable=import-error
|
|
215
|
+
|
|
226
216
|
# Alex Gaynor said:
|
|
227
217
|
# should_trace_cache is a strictly growing key: once a key is in
|
|
228
218
|
# it, it never changes. Further, the keys used to access it are
|
|
@@ -267,19 +257,19 @@ class Collector:
|
|
|
267
257
|
tracer.should_trace_cache = self.should_trace_cache
|
|
268
258
|
tracer.warn = self.warn
|
|
269
259
|
|
|
270
|
-
if hasattr(tracer,
|
|
260
|
+
if hasattr(tracer, "concur_id_func"):
|
|
271
261
|
tracer.concur_id_func = self.concur_id_func
|
|
272
|
-
if hasattr(tracer,
|
|
262
|
+
if hasattr(tracer, "file_tracers"):
|
|
273
263
|
tracer.file_tracers = self.file_tracers
|
|
274
|
-
if hasattr(tracer,
|
|
264
|
+
if hasattr(tracer, "threading"):
|
|
275
265
|
tracer.threading = self.threading
|
|
276
|
-
if hasattr(tracer,
|
|
266
|
+
if hasattr(tracer, "check_include"):
|
|
277
267
|
tracer.check_include = self.check_include
|
|
278
|
-
if hasattr(tracer,
|
|
268
|
+
if hasattr(tracer, "should_start_context"):
|
|
279
269
|
tracer.should_start_context = self.should_start_context
|
|
280
|
-
if hasattr(tracer,
|
|
270
|
+
if hasattr(tracer, "switch_context"):
|
|
281
271
|
tracer.switch_context = self.switch_context
|
|
282
|
-
if hasattr(tracer,
|
|
272
|
+
if hasattr(tracer, "disable_plugin"):
|
|
283
273
|
tracer.disable_plugin = self.disable_plugin
|
|
284
274
|
|
|
285
275
|
fn = tracer.start()
|
|
@@ -367,7 +357,7 @@ class Collector:
|
|
|
367
357
|
tracer.stop()
|
|
368
358
|
stats = tracer.get_stats()
|
|
369
359
|
if stats:
|
|
370
|
-
print("\nCoverage.py tracer stats:")
|
|
360
|
+
print(f"\nCoverage.py {tracer.__class__.__name__} stats:")
|
|
371
361
|
for k, v in human_sorted_items(stats.items()):
|
|
372
362
|
print(f"{k:>20}: {v}")
|
|
373
363
|
if self.threading:
|
|
@@ -419,7 +409,7 @@ class Collector:
|
|
|
419
409
|
plugin._coverage_enabled = False
|
|
420
410
|
disposition.trace = False
|
|
421
411
|
|
|
422
|
-
@functools.cache
|
|
412
|
+
@functools.cache # pylint: disable=method-cache-max-size-none
|
|
423
413
|
def cached_mapped_file(self, filename: str) -> str:
|
|
424
414
|
"""A locally cached version of file names mapped through file_mapper."""
|
|
425
415
|
return self.file_mapper(filename)
|
|
@@ -431,14 +421,14 @@ class Collector:
|
|
|
431
421
|
# in other threads. We try three times in case of concurrent
|
|
432
422
|
# access, hoping to get a clean copy.
|
|
433
423
|
runtime_err = None
|
|
434
|
-
for _ in range(3):
|
|
424
|
+
for _ in range(3): # pragma: part covered
|
|
435
425
|
try:
|
|
436
426
|
items = list(d.items())
|
|
437
|
-
except RuntimeError as ex:
|
|
427
|
+
except RuntimeError as ex: # pragma: cant happen
|
|
438
428
|
runtime_err = ex
|
|
439
429
|
else:
|
|
440
430
|
break
|
|
441
|
-
else:
|
|
431
|
+
else: # pragma: cant happen
|
|
442
432
|
assert isinstance(runtime_err, Exception)
|
|
443
433
|
raise runtime_err
|
|
444
434
|
|
|
@@ -488,8 +478,7 @@ class Collector:
|
|
|
488
478
|
self.covdata.add_lines(self.mapped_file_dict(line_data))
|
|
489
479
|
|
|
490
480
|
file_tracers = {
|
|
491
|
-
k: v for k, v in self.file_tracers.items()
|
|
492
|
-
if v not in self.disabled_plugins
|
|
481
|
+
k: v for k, v in self.file_tracers.items() if v not in self.disabled_plugins
|
|
493
482
|
}
|
|
494
483
|
self.covdata.add_file_tracers(self.mapped_file_dict(file_tracers))
|
|
495
484
|
|
coverage/config.py
CHANGED
|
@@ -5,24 +5,27 @@
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import base64
|
|
8
9
|
import collections
|
|
9
10
|
import configparser
|
|
10
11
|
import copy
|
|
12
|
+
import json
|
|
11
13
|
import os
|
|
12
14
|
import os.path
|
|
13
15
|
import re
|
|
14
|
-
|
|
15
|
-
from typing import (
|
|
16
|
-
Any, Callable, Union,
|
|
17
|
-
)
|
|
18
16
|
from collections.abc import Iterable
|
|
17
|
+
from typing import Any, Callable, Final, Mapping
|
|
19
18
|
|
|
20
19
|
from coverage.exceptions import ConfigError
|
|
21
|
-
from coverage.misc import
|
|
20
|
+
from coverage.misc import human_sorted_items, isolate_module, substitute_variables
|
|
22
21
|
from coverage.tomlconfig import TomlConfigParser, TomlDecodeError
|
|
23
22
|
from coverage.types import (
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
TConfigSectionIn,
|
|
24
|
+
TConfigSectionOut,
|
|
25
|
+
TConfigurable,
|
|
26
|
+
TConfigValueIn,
|
|
27
|
+
TConfigValueOut,
|
|
28
|
+
TPluginConfig,
|
|
26
29
|
)
|
|
27
30
|
|
|
28
31
|
os = isolate_module(os)
|
|
@@ -44,7 +47,7 @@ class HandyConfigParser(configparser.ConfigParser):
|
|
|
44
47
|
if our_file:
|
|
45
48
|
self.section_prefixes.append("")
|
|
46
49
|
|
|
47
|
-
def read(
|
|
50
|
+
def read( # type: ignore[override]
|
|
48
51
|
self,
|
|
49
52
|
filenames: Iterable[str],
|
|
50
53
|
encoding_unused: str | None = None,
|
|
@@ -61,16 +64,16 @@ class HandyConfigParser(configparser.ConfigParser):
|
|
|
61
64
|
return real_section
|
|
62
65
|
return None
|
|
63
66
|
|
|
64
|
-
def has_option(self, section: str, option: str) -> bool:
|
|
67
|
+
def has_option(self, section: str, option: str) -> bool: # type: ignore[override]
|
|
65
68
|
real_section = self.real_section(section)
|
|
66
69
|
if real_section is not None:
|
|
67
70
|
return super().has_option(real_section, option)
|
|
68
71
|
return False
|
|
69
72
|
|
|
70
|
-
def has_section(self, section: str) -> bool:
|
|
73
|
+
def has_section(self, section: str) -> bool: # type: ignore[override]
|
|
71
74
|
return bool(self.real_section(section))
|
|
72
75
|
|
|
73
|
-
def options(self, section: str) -> list[str]:
|
|
76
|
+
def options(self, section: str) -> list[str]: # type: ignore[override]
|
|
74
77
|
real_section = self.real_section(section)
|
|
75
78
|
if real_section is not None:
|
|
76
79
|
return super().options(real_section)
|
|
@@ -83,7 +86,7 @@ class HandyConfigParser(configparser.ConfigParser):
|
|
|
83
86
|
d[opt] = self.get(section, opt)
|
|
84
87
|
return d
|
|
85
88
|
|
|
86
|
-
def get(self, section: str, option: str, *args: Any, **kwargs: Any) -> str:
|
|
89
|
+
def get(self, section: str, option: str, *args: Any, **kwargs: Any) -> str: # type: ignore
|
|
87
90
|
"""Get a value, replacing environment variables also.
|
|
88
91
|
|
|
89
92
|
The arguments are the same as `ConfigParser.get`, but in the found
|
|
@@ -104,6 +107,11 @@ class HandyConfigParser(configparser.ConfigParser):
|
|
|
104
107
|
v = substitute_variables(v, os.environ)
|
|
105
108
|
return v
|
|
106
109
|
|
|
110
|
+
def getfile(self, section: str, option: str) -> str:
|
|
111
|
+
"""Fix up a file path setting."""
|
|
112
|
+
path = self.get(section, option)
|
|
113
|
+
return process_file_value(path)
|
|
114
|
+
|
|
107
115
|
def getlist(self, section: str, option: str) -> list[str]:
|
|
108
116
|
"""Read a list of strings.
|
|
109
117
|
|
|
@@ -132,26 +140,17 @@ class HandyConfigParser(configparser.ConfigParser):
|
|
|
132
140
|
|
|
133
141
|
"""
|
|
134
142
|
line_list = self.get(section, option)
|
|
135
|
-
|
|
136
|
-
for value in line_list.splitlines():
|
|
137
|
-
value = value.strip()
|
|
138
|
-
try:
|
|
139
|
-
re.compile(value)
|
|
140
|
-
except re.error as e:
|
|
141
|
-
raise ConfigError(
|
|
142
|
-
f"Invalid [{section}].{option} value {value!r}: {e}",
|
|
143
|
-
) from e
|
|
144
|
-
if value:
|
|
145
|
-
value_list.append(value)
|
|
146
|
-
return value_list
|
|
143
|
+
return process_regexlist(section, option, line_list.splitlines())
|
|
147
144
|
|
|
148
145
|
|
|
149
|
-
TConfigParser =
|
|
146
|
+
TConfigParser = HandyConfigParser | TomlConfigParser
|
|
150
147
|
|
|
151
148
|
|
|
152
149
|
# The default line exclusion regexes.
|
|
153
150
|
DEFAULT_EXCLUDE = [
|
|
154
151
|
r"#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER)",
|
|
152
|
+
r"^\s*(((async )?def .*?)?\)(\s*->.*?)?:\s*)?\.\.\.\s*(#|$)",
|
|
153
|
+
r"if (typing\.)?TYPE_CHECKING:",
|
|
155
154
|
]
|
|
156
155
|
|
|
157
156
|
# The default partial branch regexes, to be modified by the user.
|
|
@@ -175,6 +174,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
175
174
|
operation of coverage.py.
|
|
176
175
|
|
|
177
176
|
"""
|
|
177
|
+
|
|
178
178
|
# pylint: disable=too-many-instance-attributes
|
|
179
179
|
|
|
180
180
|
def __init__(self) -> None:
|
|
@@ -197,6 +197,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
197
197
|
self.command_line: str | None = None
|
|
198
198
|
self.concurrency: list[str] = []
|
|
199
199
|
self.context: str | None = None
|
|
200
|
+
self.core: str | None = None
|
|
200
201
|
self.cover_pylib = False
|
|
201
202
|
self.data_file = ".coverage"
|
|
202
203
|
self.debug: list[str] = []
|
|
@@ -204,6 +205,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
204
205
|
self.disable_warnings: list[str] = []
|
|
205
206
|
self.dynamic_context: str | None = None
|
|
206
207
|
self.parallel = False
|
|
208
|
+
self.patch: list[str] = []
|
|
207
209
|
self.plugins: list[str] = []
|
|
208
210
|
self.relative_files = False
|
|
209
211
|
self.run_include: list[str] = []
|
|
@@ -211,6 +213,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
211
213
|
self.sigterm = False
|
|
212
214
|
self.source: list[str] | None = None
|
|
213
215
|
self.source_pkgs: list[str] = []
|
|
216
|
+
self.source_dirs: list[str] = []
|
|
214
217
|
self.timid = False
|
|
215
218
|
self._crash: str | None = None
|
|
216
219
|
|
|
@@ -225,6 +228,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
225
228
|
self.report_omit: list[str] | None = None
|
|
226
229
|
self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
|
|
227
230
|
self.partial_list = DEFAULT_PARTIAL[:]
|
|
231
|
+
self.partial_also: list[str] = []
|
|
228
232
|
self.precision = 0
|
|
229
233
|
self.report_contexts: list[str] | None = None
|
|
230
234
|
self.show_missing = False
|
|
@@ -260,9 +264,25 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
260
264
|
self.plugin_options: dict[str, TConfigSectionOut] = {}
|
|
261
265
|
|
|
262
266
|
MUST_BE_LIST = {
|
|
263
|
-
"debug",
|
|
264
|
-
"
|
|
265
|
-
"
|
|
267
|
+
"debug",
|
|
268
|
+
"concurrency",
|
|
269
|
+
"plugins",
|
|
270
|
+
"report_omit",
|
|
271
|
+
"report_include",
|
|
272
|
+
"run_omit",
|
|
273
|
+
"run_include",
|
|
274
|
+
"patch",
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# File paths to make absolute during serialization.
|
|
278
|
+
# The pairs are (config_key, must_exist).
|
|
279
|
+
SERIALIZE_ABSPATH = {
|
|
280
|
+
("data_file", False),
|
|
281
|
+
("debug_file", False),
|
|
282
|
+
# `source` can be directories or modules, so don't abspath it if it
|
|
283
|
+
# doesn't exist.
|
|
284
|
+
("source", True),
|
|
285
|
+
("source_dirs", False),
|
|
266
286
|
}
|
|
267
287
|
|
|
268
288
|
def from_args(self, **kwargs: TConfigValueIn) -> None:
|
|
@@ -325,7 +345,9 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
325
345
|
for unknown in set(cp.options(section)) - options:
|
|
326
346
|
warn(
|
|
327
347
|
"Unrecognized option '[{}] {}=' in config file {}".format(
|
|
328
|
-
real_section,
|
|
348
|
+
real_section,
|
|
349
|
+
unknown,
|
|
350
|
+
filename,
|
|
329
351
|
),
|
|
330
352
|
)
|
|
331
353
|
|
|
@@ -360,7 +382,16 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
360
382
|
"""Return a copy of the configuration."""
|
|
361
383
|
return copy.deepcopy(self)
|
|
362
384
|
|
|
363
|
-
CONCURRENCY_CHOICES = {
|
|
385
|
+
CONCURRENCY_CHOICES: Final[set[str]] = {
|
|
386
|
+
"thread",
|
|
387
|
+
"gevent",
|
|
388
|
+
"greenlet",
|
|
389
|
+
"eventlet",
|
|
390
|
+
"multiprocessing",
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# Mutually exclusive concurrency settings.
|
|
394
|
+
LIGHT_THREADS = {"greenlet", "eventlet", "gevent"}
|
|
364
395
|
|
|
365
396
|
CONFIG_FILE_OPTIONS = [
|
|
366
397
|
# These are *args for _set_attr_from_config_option:
|
|
@@ -370,19 +401,21 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
370
401
|
# where is the section:name to read from the configuration file.
|
|
371
402
|
# type_ is the optional type to apply, by using .getTYPE to read the
|
|
372
403
|
# configuration value from the file.
|
|
373
|
-
|
|
404
|
+
#
|
|
374
405
|
# [run]
|
|
375
406
|
("branch", "run:branch", "boolean"),
|
|
376
407
|
("command_line", "run:command_line"),
|
|
377
408
|
("concurrency", "run:concurrency", "list"),
|
|
378
409
|
("context", "run:context"),
|
|
410
|
+
("core", "run:core"),
|
|
379
411
|
("cover_pylib", "run:cover_pylib", "boolean"),
|
|
380
|
-
("data_file", "run:data_file"),
|
|
412
|
+
("data_file", "run:data_file", "file"),
|
|
381
413
|
("debug", "run:debug", "list"),
|
|
382
|
-
("debug_file", "run:debug_file"),
|
|
414
|
+
("debug_file", "run:debug_file", "file"),
|
|
383
415
|
("disable_warnings", "run:disable_warnings", "list"),
|
|
384
416
|
("dynamic_context", "run:dynamic_context"),
|
|
385
417
|
("parallel", "run:parallel", "boolean"),
|
|
418
|
+
("patch", "run:patch", "list"),
|
|
386
419
|
("plugins", "run:plugins", "list"),
|
|
387
420
|
("relative_files", "run:relative_files", "boolean"),
|
|
388
421
|
("run_include", "run:include", "list"),
|
|
@@ -390,9 +423,10 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
390
423
|
("sigterm", "run:sigterm", "boolean"),
|
|
391
424
|
("source", "run:source", "list"),
|
|
392
425
|
("source_pkgs", "run:source_pkgs", "list"),
|
|
426
|
+
("source_dirs", "run:source_dirs", "list"),
|
|
393
427
|
("timid", "run:timid", "boolean"),
|
|
394
428
|
("_crash", "run:_crash"),
|
|
395
|
-
|
|
429
|
+
#
|
|
396
430
|
# [report]
|
|
397
431
|
("exclude_list", "report:exclude_lines", "regexlist"),
|
|
398
432
|
("exclude_also", "report:exclude_also", "regexlist"),
|
|
@@ -402,6 +436,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
402
436
|
("include_namespace_packages", "report:include_namespace_packages", "boolean"),
|
|
403
437
|
("partial_always_list", "report:partial_branches_always", "regexlist"),
|
|
404
438
|
("partial_list", "report:partial_branches", "regexlist"),
|
|
439
|
+
("partial_also", "report:partial_also", "regexlist"),
|
|
405
440
|
("precision", "report:precision", "int"),
|
|
406
441
|
("report_contexts", "report:contexts", "list"),
|
|
407
442
|
("report_include", "report:include", "list"),
|
|
@@ -410,27 +445,27 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
410
445
|
("skip_covered", "report:skip_covered", "boolean"),
|
|
411
446
|
("skip_empty", "report:skip_empty", "boolean"),
|
|
412
447
|
("sort", "report:sort"),
|
|
413
|
-
|
|
448
|
+
#
|
|
414
449
|
# [html]
|
|
415
450
|
("extra_css", "html:extra_css"),
|
|
416
|
-
("html_dir", "html:directory"),
|
|
451
|
+
("html_dir", "html:directory", "file"),
|
|
417
452
|
("html_skip_covered", "html:skip_covered", "boolean"),
|
|
418
453
|
("html_skip_empty", "html:skip_empty", "boolean"),
|
|
419
454
|
("html_title", "html:title"),
|
|
420
455
|
("show_contexts", "html:show_contexts", "boolean"),
|
|
421
|
-
|
|
456
|
+
#
|
|
422
457
|
# [xml]
|
|
423
|
-
("xml_output", "xml:output"),
|
|
458
|
+
("xml_output", "xml:output", "file"),
|
|
424
459
|
("xml_package_depth", "xml:package_depth", "int"),
|
|
425
|
-
|
|
460
|
+
#
|
|
426
461
|
# [json]
|
|
427
|
-
("json_output", "json:output"),
|
|
462
|
+
("json_output", "json:output", "file"),
|
|
428
463
|
("json_pretty_print", "json:pretty_print", "boolean"),
|
|
429
464
|
("json_show_contexts", "json:show_contexts", "boolean"),
|
|
430
|
-
|
|
465
|
+
#
|
|
431
466
|
# [lcov]
|
|
432
|
-
("lcov_output", "lcov:output"),
|
|
433
|
-
("lcov_line_checksums", "lcov:line_checksums", "boolean")
|
|
467
|
+
("lcov_output", "lcov:output", "file"),
|
|
468
|
+
("lcov_line_checksums", "lcov:line_checksums", "boolean"),
|
|
434
469
|
]
|
|
435
470
|
|
|
436
471
|
def _set_attr_from_config_option(
|
|
@@ -447,7 +482,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
447
482
|
"""
|
|
448
483
|
section, option = where.split(":")
|
|
449
484
|
if cp.has_option(section, option):
|
|
450
|
-
method = getattr(cp, "get"
|
|
485
|
+
method = getattr(cp, f"get{type_}")
|
|
451
486
|
setattr(self, attr, method(section, option))
|
|
452
487
|
return True
|
|
453
488
|
return False
|
|
@@ -468,7 +503,13 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
468
503
|
"""
|
|
469
504
|
# Special-cased options.
|
|
470
505
|
if option_name == "paths":
|
|
471
|
-
|
|
506
|
+
# This is ugly, but type-checks and ensures the values are close
|
|
507
|
+
# to right.
|
|
508
|
+
self.paths = {}
|
|
509
|
+
assert isinstance(value, Mapping)
|
|
510
|
+
for k, v in value.items():
|
|
511
|
+
assert isinstance(v, Iterable)
|
|
512
|
+
self.paths[k] = list(v)
|
|
472
513
|
return
|
|
473
514
|
|
|
474
515
|
# Check all the hard-coded options.
|
|
@@ -481,7 +522,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
481
522
|
# See if it's a plugin option.
|
|
482
523
|
plugin_name, _, key = option_name.partition(":")
|
|
483
524
|
if key and plugin_name in self.plugins:
|
|
484
|
-
self.plugin_options.setdefault(plugin_name, {})[key] = value
|
|
525
|
+
self.plugin_options.setdefault(plugin_name, {})[key] = value # type: ignore[index]
|
|
485
526
|
return
|
|
486
527
|
|
|
487
528
|
# If we get here, we didn't find the option.
|
|
@@ -499,7 +540,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
499
540
|
"""
|
|
500
541
|
# Special-cased options.
|
|
501
542
|
if option_name == "paths":
|
|
502
|
-
return self.paths
|
|
543
|
+
return self.paths
|
|
503
544
|
|
|
504
545
|
# Check all the hard-coded options.
|
|
505
546
|
for option_spec in self.CONFIG_FILE_OPTIONS:
|
|
@@ -515,26 +556,82 @@ class CoverageConfig(TConfigurable, TPluginConfig):
|
|
|
515
556
|
# If we get here, we didn't find the option.
|
|
516
557
|
raise ConfigError(f"No such option: {option_name!r}")
|
|
517
558
|
|
|
518
|
-
def post_process_file(self, path: str) -> str:
|
|
519
|
-
"""Make final adjustments to a file path to make it usable."""
|
|
520
|
-
return os.path.expanduser(path)
|
|
521
|
-
|
|
522
559
|
def post_process(self) -> None:
|
|
523
560
|
"""Make final adjustments to settings to make them usable."""
|
|
524
|
-
self.
|
|
525
|
-
|
|
526
|
-
self.xml_output = self.post_process_file(self.xml_output)
|
|
527
|
-
self.paths = {
|
|
528
|
-
k: [self.post_process_file(f) for f in v]
|
|
529
|
-
for k, v in self.paths.items()
|
|
530
|
-
}
|
|
561
|
+
self.paths = {k: [process_file_value(f) for f in v] for k, v in self.paths.items()}
|
|
562
|
+
|
|
531
563
|
self.exclude_list += self.exclude_also
|
|
564
|
+
self.partial_list += self.partial_also
|
|
565
|
+
|
|
566
|
+
if "subprocess" in self.patch:
|
|
567
|
+
self.parallel = True
|
|
568
|
+
|
|
569
|
+
# We can handle a few concurrency options here, but only one at a time.
|
|
570
|
+
concurrencies = set(self.concurrency)
|
|
571
|
+
unknown = concurrencies - self.CONCURRENCY_CHOICES
|
|
572
|
+
if unknown:
|
|
573
|
+
show = ", ".join(sorted(unknown))
|
|
574
|
+
raise ConfigError(f"Unknown concurrency choices: {show}")
|
|
575
|
+
light_threads = concurrencies & self.LIGHT_THREADS
|
|
576
|
+
if len(light_threads) > 1:
|
|
577
|
+
show = ", ".join(sorted(light_threads))
|
|
578
|
+
raise ConfigError(f"Conflicting concurrency settings: {show}")
|
|
532
579
|
|
|
533
580
|
def debug_info(self) -> list[tuple[str, Any]]:
|
|
534
581
|
"""Make a list of (name, value) pairs for writing debug info."""
|
|
535
|
-
return human_sorted_items(
|
|
536
|
-
|
|
537
|
-
|
|
582
|
+
return human_sorted_items((k, v) for k, v in self.__dict__.items() if not k.startswith("_"))
|
|
583
|
+
|
|
584
|
+
def serialize(self) -> str:
|
|
585
|
+
"""Convert to a string that can be ingested with `deserialize`.
|
|
586
|
+
|
|
587
|
+
File paths used by `coverage run` are made absolute to ensure the
|
|
588
|
+
deserialized config will refer to the same files.
|
|
589
|
+
"""
|
|
590
|
+
data = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
|
591
|
+
for k, must_exist in self.SERIALIZE_ABSPATH:
|
|
592
|
+
abs_fn = abs_path_if_exists if must_exist else os.path.abspath
|
|
593
|
+
v = data[k]
|
|
594
|
+
if isinstance(v, list):
|
|
595
|
+
v = list(map(abs_fn, v))
|
|
596
|
+
elif isinstance(v, str):
|
|
597
|
+
v = abs_fn(v)
|
|
598
|
+
data[k] = v
|
|
599
|
+
return base64.b64encode(json.dumps(data).encode()).decode()
|
|
600
|
+
|
|
601
|
+
@classmethod
|
|
602
|
+
def deserialize(cls, config_str: str) -> CoverageConfig:
|
|
603
|
+
"""Take a string from `serialize`, and make a CoverageConfig."""
|
|
604
|
+
data = json.loads(base64.b64decode(config_str.encode()).decode())
|
|
605
|
+
config = cls()
|
|
606
|
+
config.__dict__.update(data)
|
|
607
|
+
return config
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def process_file_value(path: str) -> str:
|
|
611
|
+
"""Make adjustments to a file path to make it usable."""
|
|
612
|
+
return os.path.expanduser(path)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def abs_path_if_exists(path: str) -> str:
|
|
616
|
+
"""os.path.abspath, but only if the path exists."""
|
|
617
|
+
if os.path.exists(path):
|
|
618
|
+
return os.path.abspath(path)
|
|
619
|
+
else:
|
|
620
|
+
return path
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def process_regexlist(name: str, option: str, values: list[str]) -> list[str]:
|
|
624
|
+
"""Check the values in a regex list and keep the non-blank ones."""
|
|
625
|
+
value_list = []
|
|
626
|
+
for value in values:
|
|
627
|
+
value = value.strip()
|
|
628
|
+
try:
|
|
629
|
+
re.compile(value)
|
|
630
|
+
except re.error as e:
|
|
631
|
+
raise ConfigError(f"Invalid [{name}].{option} value {value!r}: {e}") from e
|
|
632
|
+
if value:
|
|
633
|
+
value_list.append(value)
|
|
634
|
+
return value_list
|
|
538
635
|
|
|
539
636
|
|
|
540
637
|
def config_files_to_try(config_file: bool | str) -> list[tuple[str, bool, bool]]:
|
|
@@ -548,7 +645,7 @@ def config_files_to_try(config_file: bool | str) -> list[tuple[str, bool, bool]]
|
|
|
548
645
|
# True, so make it so.
|
|
549
646
|
if config_file == ".coveragerc":
|
|
550
647
|
config_file = True
|
|
551
|
-
specified_file =
|
|
648
|
+
specified_file = config_file is not True
|
|
552
649
|
if not specified_file:
|
|
553
650
|
# No file was specified. Check COVERAGE_RCFILE.
|
|
554
651
|
rcfile = os.getenv("COVERAGE_RCFILE")
|
|
@@ -607,11 +704,18 @@ def read_coverage_config(
|
|
|
607
704
|
env_data_file = os.getenv("COVERAGE_FILE")
|
|
608
705
|
if env_data_file:
|
|
609
706
|
config.data_file = env_data_file
|
|
707
|
+
|
|
610
708
|
# $set_env.py: COVERAGE_DEBUG - Debug options: https://coverage.rtfd.io/cmd.html#debug
|
|
611
709
|
debugs = os.getenv("COVERAGE_DEBUG")
|
|
612
710
|
if debugs:
|
|
613
711
|
config.debug.extend(d.strip() for d in debugs.split(","))
|
|
614
712
|
|
|
713
|
+
# Read the COVERAGE_CORE environment variable for backward compatibility,
|
|
714
|
+
# and because we use it in the test suite to pick a specific core.
|
|
715
|
+
env_core = os.getenv("COVERAGE_CORE")
|
|
716
|
+
if env_core:
|
|
717
|
+
config.core = env_core
|
|
718
|
+
|
|
615
719
|
# 4) from constructor arguments:
|
|
616
720
|
config.from_args(**kwargs)
|
|
617
721
|
|
coverage/context.py
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from types import FrameType
|
|
9
|
-
from typing import cast
|
|
10
8
|
from collections.abc import Sequence
|
|
9
|
+
from types import FrameType
|
|
11
10
|
|
|
12
11
|
from coverage.types import TShouldStartContextFn
|
|
13
12
|
|
|
@@ -65,11 +64,11 @@ def qualname_from_frame(frame: FrameType) -> str | None:
|
|
|
65
64
|
func = frame.f_globals.get(fname)
|
|
66
65
|
if func is None:
|
|
67
66
|
return None
|
|
68
|
-
return
|
|
67
|
+
return f"{func.__module__}.{fname}"
|
|
69
68
|
|
|
70
69
|
func = getattr(method, "__func__", None)
|
|
71
70
|
if func is None:
|
|
72
71
|
cls = self.__class__
|
|
73
|
-
return
|
|
72
|
+
return f"{cls.__module__}.{cls.__name__}.{fname}"
|
|
74
73
|
|
|
75
|
-
return
|
|
74
|
+
return f"{func.__module__}.{func.__qualname__}"
|