coverage 7.10.6__cp313-cp313-musllinux_1_2_aarch64.whl → 7.11.0__cp313-cp313-musllinux_1_2_aarch64.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/cmdline.py CHANGED
@@ -24,7 +24,7 @@ from coverage.control import DEFAULT_DATAFILE
24
24
  from coverage.core import CTRACER_FILE
25
25
  from coverage.data import combinable_files, debug_data_file
26
26
  from coverage.debug import info_header, short_stack, write_formatted_info
27
- from coverage.exceptions import NoSource, _BaseCoverageException, _ExceptionDuringRun
27
+ from coverage.exceptions import NoSource, CoverageException, _ExceptionDuringRun
28
28
  from coverage.execfile import PyRunner
29
29
  from coverage.results import display_covered, should_fail_under
30
30
  from coverage.version import __url__
@@ -1139,9 +1139,11 @@ def main(argv: list[str] | None = None) -> int | None:
1139
1139
  # exception.
1140
1140
  traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter
1141
1141
  status = ERR
1142
- except _BaseCoverageException as err:
1142
+ except CoverageException as err:
1143
1143
  # A controlled error inside coverage.py: print the message to the user.
1144
1144
  msg = err.args[0]
1145
+ if err.slug:
1146
+ msg = f"{msg.rstrip('.')}; see {__url__}/messages.html#error-{err.slug}"
1145
1147
  print(msg)
1146
1148
  status = ERR
1147
1149
  except SystemExit as err:
coverage/collector.py CHANGED
@@ -146,12 +146,12 @@ class Collector:
146
146
  self.concur_id_func = greenlet.getcurrent
147
147
  elif "eventlet" in concurrencies:
148
148
  tried = "eventlet"
149
- import eventlet.greenthread # pylint: disable=import-error,useless-suppression
149
+ import eventlet.greenthread
150
150
 
151
151
  self.concur_id_func = eventlet.greenthread.getcurrent
152
152
  elif "gevent" in concurrencies:
153
153
  tried = "gevent"
154
- import gevent # pylint: disable=import-error,useless-suppression
154
+ import gevent
155
155
 
156
156
  self.concur_id_func = gevent.getcurrent
157
157
 
coverage/config.py CHANGED
@@ -14,7 +14,7 @@ import os
14
14
  import os.path
15
15
  import re
16
16
  from collections.abc import Iterable
17
- from typing import Any, Callable, Final, Mapping, Union
17
+ from typing import Any, Callable, Final, Mapping
18
18
 
19
19
  from coverage.exceptions import ConfigError
20
20
  from coverage.misc import human_sorted_items, isolate_module, substitute_variables
@@ -143,7 +143,7 @@ class HandyConfigParser(configparser.ConfigParser):
143
143
  return process_regexlist(section, option, line_list.splitlines())
144
144
 
145
145
 
146
- TConfigParser = Union[HandyConfigParser, TomlConfigParser]
146
+ TConfigParser = HandyConfigParser | TomlConfigParser
147
147
 
148
148
 
149
149
  # The default line exclusion regexes.
@@ -568,7 +568,7 @@ class CoverageConfig(TConfigurable, TPluginConfig):
568
568
  return human_sorted_items((k, v) for k, v in self.__dict__.items() if not k.startswith("_"))
569
569
 
570
570
  def serialize(self) -> str:
571
- """Convert to a string that can be ingested with `deserialize_config`.
571
+ """Convert to a string that can be ingested with `deserialize`.
572
572
 
573
573
  File paths used by `coverage run` are made absolute to ensure the
574
574
  deserialized config will refer to the same files.
@@ -584,6 +584,14 @@ class CoverageConfig(TConfigurable, TPluginConfig):
584
584
  data[k] = v
585
585
  return base64.b64encode(json.dumps(data).encode()).decode()
586
586
 
587
+ @classmethod
588
+ def deserialize(cls, config_str: str) -> CoverageConfig:
589
+ """Take a string from `serialize`, and make a CoverageConfig."""
590
+ data = json.loads(base64.b64decode(config_str.encode()).decode())
591
+ config = cls()
592
+ config.__dict__.update(data)
593
+ return config
594
+
587
595
 
588
596
  def process_file_value(path: str) -> str:
589
597
  """Make adjustments to a file path to make it usable."""
@@ -707,11 +715,3 @@ def read_coverage_config(
707
715
  config.post_process()
708
716
 
709
717
  return config
710
-
711
-
712
- def deserialize_config(config_str: str) -> CoverageConfig:
713
- """Take a string from CoverageConfig.serialize, and make a CoverageConfig."""
714
- data = json.loads(base64.b64decode(config_str.encode()).decode())
715
- config = CoverageConfig()
716
- config.__dict__.update(data)
717
- return config
coverage/control.py CHANGED
@@ -19,12 +19,12 @@ import time
19
19
  import warnings
20
20
  from collections.abc import Iterable, Iterator
21
21
  from types import FrameType
22
- from typing import IO, Any, Callable, Union, cast
22
+ from typing import IO, Any, Callable, cast
23
23
 
24
24
  from coverage import env
25
25
  from coverage.annotate import AnnotateReporter
26
26
  from coverage.collector import Collector
27
- from coverage.config import CoverageConfig, deserialize_config, read_coverage_config
27
+ from coverage.config import CoverageConfig, read_coverage_config
28
28
  from coverage.context import combine_context_switchers, should_start_context_test_function
29
29
  from coverage.core import CTRACER_FILE, Core
30
30
  from coverage.data import CoverageData, combine_parallel_data
@@ -67,6 +67,7 @@ from coverage.types import (
67
67
  TLineNo,
68
68
  TMorf,
69
69
  )
70
+ from coverage.version import __url__
70
71
  from coverage.xmlreport import XmlReporter
71
72
 
72
73
  os = isolate_module(os)
@@ -297,7 +298,7 @@ class Coverage(TConfigurable):
297
298
  self._debug: DebugControl = NoDebugging()
298
299
  self._inorout: InOrOut | None = None
299
300
  self._plugins: Plugins = Plugins()
300
- self._plugin_override = cast(Union[Iterable[TCoverageInit], None], plugins)
301
+ self._plugin_override = cast(Iterable[TCoverageInit] | None, plugins)
301
302
  self._data: CoverageData | None = None
302
303
  self._data_to_close: list[CoverageData] = []
303
304
  self._core: Core | None = None
@@ -320,7 +321,7 @@ class Coverage(TConfigurable):
320
321
 
321
322
  # Build our configuration from a number of sources.
322
323
  if isinstance(config_file, str) and config_file.startswith(CONFIG_DATA_PREFIX):
323
- self.config = deserialize_config(config_file[len(CONFIG_DATA_PREFIX) :])
324
+ self.config = CoverageConfig.deserialize(config_file[len(CONFIG_DATA_PREFIX) :])
324
325
  else:
325
326
  if not isinstance(config_file, bool):
326
327
  config_file = os.fspath(config_file)
@@ -479,7 +480,7 @@ class Coverage(TConfigurable):
479
480
 
480
481
  self._warnings.append(msg)
481
482
  if slug:
482
- msg = f"{msg} ({slug})"
483
+ msg = f"{msg} ({slug}); see {__url__}/messages.html#warning-{slug}"
483
484
  if self._debug.should("pid"):
484
485
  msg = f"[{os.getpid()}] {msg}"
485
486
  warnings.warn(msg, category=CoverageWarning, stacklevel=2)
coverage/debug.py CHANGED
@@ -155,14 +155,15 @@ def info_formatter(info: Iterable[tuple[str, Any]]) -> Iterator[str]:
155
155
  if data == []:
156
156
  data = "-none-"
157
157
  prefix = f"{label:>{LABEL_LEN}}: "
158
- if isinstance(data, tuple) and len(str(data)) < 30:
159
- yield f"{prefix}{data}"
160
- elif isinstance(data, (list, set, tuple)):
161
- for e in data:
162
- yield f"{prefix}{e}"
163
- prefix = " " * (LABEL_LEN + 2)
164
- else:
165
- yield f"{prefix}{data}"
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}"
166
167
 
167
168
 
168
169
  def write_formatted_info(
coverage/env.py CHANGED
@@ -43,7 +43,7 @@ else:
43
43
  GIL = getattr(sys, "_is_gil_enabled", lambda: True)()
44
44
 
45
45
  # Do we ship compiled coveragepy wheels for this version?
46
- SHIPPING_WHEELS = CPYTHON and PYVERSION[:2] <= (3, 13)
46
+ SHIPPING_WHEELS = CPYTHON and PYVERSION[:2] <= (3, 14)
47
47
 
48
48
  # Should we default to sys.monitoring?
49
49
  SYSMON_DEFAULT = CPYTHON and PYVERSION >= (3, 14)
@@ -53,63 +53,6 @@ SYSMON_DEFAULT = CPYTHON and PYVERSION >= (3, 14)
53
53
  class PYBEHAVIOR:
54
54
  """Flags indicating this Python's behavior."""
55
55
 
56
- # Does Python conform to PEP626, Precise line numbers for debugging and other tools.
57
- # https://www.python.org/dev/peps/pep-0626
58
- pep626 = (PYVERSION > (3, 10, 0, "alpha", 4)) # fmt: skip
59
-
60
- # Is "if __debug__" optimized away?
61
- optimize_if_debug = not pep626
62
-
63
- # Is "if not __debug__" optimized away? The exact details have changed
64
- # across versions.
65
- optimize_if_not_debug = 1 if pep626 else 2
66
-
67
- # 3.7 changed how functions with only docstrings are numbered.
68
- docstring_only_function = (not PYPY) and (PYVERSION <= (3, 10))
69
-
70
- # Lines after break/continue/return/raise are no longer compiled into the
71
- # bytecode. They used to be marked as missing, now they aren't executable.
72
- omit_after_jump = pep626 or PYPY
73
-
74
- # PyPy has always omitted statements after return.
75
- omit_after_return = omit_after_jump or PYPY
76
-
77
- # Optimize away unreachable try-else clauses.
78
- optimize_unreachable_try_else = pep626
79
-
80
- # Modules used to have firstlineno equal to the line number of the first
81
- # real line of code. Now they always start at 1.
82
- module_firstline_1 = pep626
83
-
84
- # Are "if 0:" lines (and similar) kept in the compiled code?
85
- keep_constant_test = pep626
86
-
87
- # When leaving a with-block, do we visit the with-line again for the exit?
88
- # For example, wwith.py:
89
- #
90
- # with open("/tmp/test", "w") as f1:
91
- # a = 2
92
- # with open("/tmp/test2", "w") as f3:
93
- # print(4)
94
- #
95
- # % python3.9 -m trace -t wwith.py | grep wwith
96
- # --- modulename: wwith, funcname: <module>
97
- # wwith.py(1): with open("/tmp/test", "w") as f1:
98
- # wwith.py(2): a = 2
99
- # wwith.py(3): with open("/tmp/test2", "w") as f3:
100
- # wwith.py(4): print(4)
101
- #
102
- # % python3.10 -m trace -t wwith.py | grep wwith
103
- # --- modulename: wwith, funcname: <module>
104
- # wwith.py(1): with open("/tmp/test", "w") as f1:
105
- # wwith.py(2): a = 2
106
- # wwith.py(3): with open("/tmp/test2", "w") as f3:
107
- # wwith.py(4): print(4)
108
- # wwith.py(3): with open("/tmp/test2", "w") as f3:
109
- # wwith.py(1): with open("/tmp/test", "w") as f1:
110
- #
111
- exit_through_with = (PYVERSION >= (3, 10, 0, "beta")) # fmt: skip
112
-
113
56
  # When leaving a with-block, do we visit the with-line exactly,
114
57
  # or the context managers in inner-out order?
115
58
  #
@@ -147,12 +90,6 @@ class PYBEHAVIOR:
147
90
 
148
91
  exit_with_through_ctxmgr = (PYVERSION >= (3, 12, 6)) # fmt: skip
149
92
 
150
- # Match-case construct.
151
- match_case = (PYVERSION >= (3, 10)) # fmt: skip
152
-
153
- # Some words are keywords in some places, identifiers in other places.
154
- soft_keywords = (PYVERSION >= (3, 10)) # fmt: skip
155
-
156
93
  # f-strings are parsed as code, pep 701
157
94
  fstring_syntax = (PYVERSION >= (3, 12)) # fmt: skip
158
95
 
coverage/exceptions.py CHANGED
@@ -5,20 +5,18 @@
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ from typing import Any
8
9
 
9
- class _BaseCoverageException(Exception):
10
- """The base-base of all Coverage exceptions."""
11
10
 
12
- pass
13
-
14
-
15
- class CoverageException(_BaseCoverageException):
11
+ class CoverageException(Exception):
16
12
  """The base class of all exceptions raised by Coverage.py."""
17
13
 
18
- pass
14
+ def __init__(self, *args: Any, slug: str | None = None) -> None:
15
+ super().__init__(*args)
16
+ self.slug = slug
19
17
 
20
18
 
21
- class ConfigError(_BaseCoverageException):
19
+ class ConfigError(CoverageException):
22
20
  """A problem with a config file, or a value in one."""
23
21
 
24
22
  pass
coverage/html.py CHANGED
@@ -32,7 +32,7 @@ from coverage.misc import (
32
32
  stdout_link,
33
33
  )
34
34
  from coverage.report_core import get_analysis_to_report
35
- from coverage.results import Analysis, Numbers
35
+ from coverage.results import Analysis, AnalysisNarrower, Numbers
36
36
  from coverage.templite import Templite
37
37
  from coverage.types import TLineNo, TMorf
38
38
  from coverage.version import __url__
@@ -582,13 +582,21 @@ class HtmlReporter:
582
582
 
583
583
  for noun in region_nouns:
584
584
  page_data = self.index_pages[noun]
585
- outside_lines = set(range(1, num_lines + 1))
586
585
 
586
+ outside_lines = set(range(1, num_lines + 1))
587
587
  for region in regions:
588
588
  if region.kind != noun:
589
589
  continue
590
590
  outside_lines -= region.lines
591
- analysis = ftr.analysis.narrow(region.lines)
591
+
592
+ narrower = AnalysisNarrower(ftr.analysis)
593
+ narrower.add_regions(r.lines for r in regions if r.kind == noun)
594
+ narrower.add_regions([outside_lines])
595
+
596
+ for region in regions:
597
+ if region.kind != noun:
598
+ continue
599
+ analysis = narrower.narrow(region.lines)
592
600
  if not self.should_report(analysis, page_data):
593
601
  continue
594
602
  sorting_name = region.name.rpartition(".")[-1].lstrip("_")
@@ -605,7 +613,7 @@ class HtmlReporter:
605
613
  )
606
614
  )
607
615
 
608
- analysis = ftr.analysis.narrow(outside_lines)
616
+ analysis = narrower.narrow(outside_lines)
609
617
  if self.should_report(analysis, page_data):
610
618
  page_data.summaries.append(
611
619
  IndexItem(
coverage/jsonreport.py CHANGED
@@ -13,7 +13,7 @@ from typing import IO, TYPE_CHECKING, Any
13
13
 
14
14
  from coverage import __version__
15
15
  from coverage.report_core import get_analysis_to_report
16
- from coverage.results import Analysis, Numbers
16
+ from coverage.results import Analysis, AnalysisNarrower, Numbers
17
17
  from coverage.types import TLineNo, TMorf
18
18
 
19
19
  if TYPE_CHECKING:
@@ -128,21 +128,30 @@ class JsonReporter:
128
128
  )
129
129
 
130
130
  num_lines = len(file_reporter.source().splitlines())
131
+ regions = file_reporter.code_regions()
131
132
  for noun, plural in file_reporter.code_region_kinds():
132
- reported_file[plural] = region_data = {}
133
133
  outside_lines = set(range(1, num_lines + 1))
134
- for region in file_reporter.code_regions():
134
+ for region in regions:
135
135
  if region.kind != noun:
136
136
  continue
137
137
  outside_lines -= region.lines
138
+
139
+ narrower = AnalysisNarrower(analysis)
140
+ narrower.add_regions(r.lines for r in regions if r.kind == noun)
141
+ narrower.add_regions([outside_lines])
142
+
143
+ reported_file[plural] = region_data = {}
144
+ for region in regions:
145
+ if region.kind != noun:
146
+ continue
138
147
  region_data[region.name] = self.make_region_data(
139
148
  coverage_data,
140
- analysis.narrow(region.lines),
149
+ narrower.narrow(region.lines),
141
150
  )
142
151
 
143
152
  region_data[""] = self.make_region_data(
144
153
  coverage_data,
145
- analysis.narrow(outside_lines),
154
+ narrower.narrow(outside_lines),
146
155
  )
147
156
  return reported_file
148
157
 
coverage/lcovreport.py CHANGED
@@ -13,7 +13,7 @@ from typing import IO, TYPE_CHECKING
13
13
 
14
14
  from coverage.plugin import FileReporter
15
15
  from coverage.report_core import get_analysis_to_report
16
- from coverage.results import Analysis, Numbers
16
+ from coverage.results import Analysis, AnalysisNarrower, Numbers
17
17
  from coverage.types import TMorf
18
18
 
19
19
  if TYPE_CHECKING:
@@ -81,12 +81,15 @@ def lcov_functions(
81
81
  if not functions:
82
82
  return
83
83
 
84
+ narrower = AnalysisNarrower(file_analysis)
85
+ narrower.add_regions(r.lines for _, _, r in functions)
86
+
84
87
  functions.sort()
85
88
  functions_hit = 0
86
89
  for first_line, last_line, region in functions:
87
90
  # A function counts as having been executed if any of it has been
88
91
  # executed.
89
- analysis = file_analysis.narrow(region.lines)
92
+ analysis = narrower.narrow(region.lines)
90
93
  hit = int(analysis.numbers.n_executed > 0)
91
94
  functions_hit += hit
92
95
 
coverage/misc.py CHANGED
@@ -163,31 +163,32 @@ class Hasher:
163
163
  def update(self, v: Any) -> None:
164
164
  """Add `v` to the hash, recursively if needed."""
165
165
  self.hash.update(str(type(v)).encode("utf-8"))
166
- if isinstance(v, str):
167
- self.hash.update(v.encode("utf-8"))
168
- elif isinstance(v, bytes):
169
- self.hash.update(v)
170
- elif v is None:
171
- pass
172
- elif isinstance(v, (int, float)):
173
- self.hash.update(str(v).encode("utf-8"))
174
- elif isinstance(v, (tuple, list)):
175
- for e in v:
176
- self.update(e)
177
- elif isinstance(v, dict):
178
- keys = v.keys()
179
- for k in sorted(keys):
180
- self.update(k)
181
- self.update(v[k])
182
- else:
183
- for k in dir(v):
184
- if k.startswith("__"):
185
- continue
186
- a = getattr(v, k)
187
- if inspect.isroutine(a):
188
- continue
189
- self.update(k)
190
- self.update(a)
166
+ match v:
167
+ case None:
168
+ pass
169
+ case str():
170
+ self.hash.update(v.encode("utf-8"))
171
+ case bytes():
172
+ self.hash.update(v)
173
+ case int() | float():
174
+ self.hash.update(str(v).encode("utf-8"))
175
+ case tuple() | list():
176
+ for e in v:
177
+ self.update(e)
178
+ case dict():
179
+ keys = v.keys()
180
+ for k in sorted(keys):
181
+ self.update(k)
182
+ self.update(v[k])
183
+ case _:
184
+ for k in dir(v):
185
+ if k.startswith("__"):
186
+ continue
187
+ a = getattr(v, k)
188
+ if inspect.isroutine(a):
189
+ continue
190
+ self.update(k)
191
+ self.update(a)
191
192
  self.hash.update(b".")
192
193
 
193
194
  def hexdigest(self) -> str: