coverage 7.6.10__cp312-cp312-musllinux_1_2_aarch64.whl → 7.12.0__cp312-cp312-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/__init__.py +3 -1
- coverage/__main__.py +3 -1
- coverage/annotate.py +2 -3
- coverage/bytecode.py +178 -4
- coverage/cmdline.py +330 -155
- coverage/collector.py +32 -43
- coverage/config.py +167 -63
- coverage/context.py +5 -6
- coverage/control.py +165 -86
- coverage/core.py +71 -34
- coverage/data.py +4 -5
- coverage/debug.py +113 -57
- coverage/disposition.py +2 -1
- coverage/env.py +29 -78
- coverage/exceptions.py +29 -7
- coverage/execfile.py +19 -14
- coverage/files.py +24 -19
- coverage/html.py +118 -75
- coverage/htmlfiles/coverage_html.js +12 -10
- coverage/htmlfiles/index.html +45 -10
- coverage/htmlfiles/pyfile.html +2 -2
- coverage/htmlfiles/style.css +54 -6
- coverage/htmlfiles/style.scss +85 -3
- coverage/inorout.py +62 -45
- coverage/jsonreport.py +22 -9
- coverage/lcovreport.py +16 -18
- coverage/misc.py +51 -47
- coverage/multiproc.py +12 -7
- coverage/numbits.py +4 -5
- coverage/parser.py +150 -251
- coverage/patch.py +166 -0
- coverage/phystokens.py +25 -26
- coverage/plugin.py +14 -14
- coverage/plugin_support.py +37 -36
- coverage/python.py +13 -14
- coverage/pytracer.py +31 -33
- coverage/regions.py +3 -2
- coverage/report.py +60 -44
- coverage/report_core.py +7 -10
- coverage/results.py +152 -68
- coverage/sqldata.py +261 -211
- coverage/sqlitedb.py +37 -29
- coverage/sysmon.py +237 -162
- coverage/templite.py +19 -7
- coverage/tomlconfig.py +13 -13
- coverage/tracer.cpython-312-aarch64-linux-musl.so +0 -0
- coverage/tracer.pyi +3 -1
- coverage/types.py +26 -23
- coverage/version.py +4 -19
- coverage/xmlreport.py +17 -14
- {coverage-7.6.10.dist-info → coverage-7.12.0.dist-info}/METADATA +50 -28
- coverage-7.12.0.dist-info/RECORD +59 -0
- {coverage-7.6.10.dist-info → coverage-7.12.0.dist-info}/WHEEL +1 -1
- coverage-7.6.10.dist-info/RECORD +0 -58
- {coverage-7.6.10.dist-info → coverage-7.12.0.dist-info}/entry_points.txt +0 -0
- {coverage-7.6.10.dist-info → coverage-7.12.0.dist-info/licenses}/LICENSE.txt +0 -0
- {coverage-7.6.10.dist-info → coverage-7.12.0.dist-info}/top_level.txt +0 -0
coverage/results.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
-
# For details: https://github.com/
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
|
3
3
|
|
|
4
4
|
"""Results of coverage measurement."""
|
|
5
5
|
|
|
@@ -7,8 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
import collections
|
|
9
9
|
import dataclasses
|
|
10
|
-
|
|
11
|
-
from collections.abc import Container, Iterable
|
|
10
|
+
from collections.abc import Iterable
|
|
12
11
|
from typing import TYPE_CHECKING
|
|
13
12
|
|
|
14
13
|
from coverage.exceptions import ConfigError
|
|
@@ -42,9 +41,7 @@ def analysis_from_file_reporter(
|
|
|
42
41
|
for fromno, tono in arc_possibilities_set:
|
|
43
42
|
dests[fromno].add(tono)
|
|
44
43
|
single_dests = {
|
|
45
|
-
fromno: list(tonos)[0]
|
|
46
|
-
for fromno, tonos in dests.items()
|
|
47
|
-
if len(tonos) == 1
|
|
44
|
+
fromno: list(tonos)[0] for fromno, tonos in dests.items() if len(tonos) == 1
|
|
48
45
|
}
|
|
49
46
|
new_arcs = set()
|
|
50
47
|
for fromno, tono in arcs:
|
|
@@ -100,8 +97,8 @@ class Analysis:
|
|
|
100
97
|
if self.has_arcs:
|
|
101
98
|
n_branches = self._total_branches()
|
|
102
99
|
mba = self.missing_branch_arcs()
|
|
103
|
-
n_partial_branches = sum(len(v) for k,v in mba.items() if k not in self.missing)
|
|
104
|
-
n_missing_branches = sum(len(v) for k,v in mba.items())
|
|
100
|
+
n_partial_branches = sum(len(v) for k, v in mba.items() if k not in self.missing)
|
|
101
|
+
n_missing_branches = sum(len(v) for k, v in mba.items())
|
|
105
102
|
else:
|
|
106
103
|
n_branches = n_partial_branches = n_missing_branches = 0
|
|
107
104
|
|
|
@@ -116,50 +113,6 @@ class Analysis:
|
|
|
116
113
|
n_missing_branches=n_missing_branches,
|
|
117
114
|
)
|
|
118
115
|
|
|
119
|
-
def narrow(self, lines: Container[TLineNo]) -> Analysis:
|
|
120
|
-
"""Create a narrowed Analysis.
|
|
121
|
-
|
|
122
|
-
The current analysis is copied to make a new one that only considers
|
|
123
|
-
the lines in `lines`.
|
|
124
|
-
"""
|
|
125
|
-
|
|
126
|
-
statements = {lno for lno in self.statements if lno in lines}
|
|
127
|
-
excluded = {lno for lno in self.excluded if lno in lines}
|
|
128
|
-
executed = {lno for lno in self.executed if lno in lines}
|
|
129
|
-
|
|
130
|
-
if self.has_arcs:
|
|
131
|
-
arc_possibilities_set = {
|
|
132
|
-
(a, b) for a, b in self.arc_possibilities_set
|
|
133
|
-
if a in lines or b in lines
|
|
134
|
-
}
|
|
135
|
-
arcs_executed_set = {
|
|
136
|
-
(a, b) for a, b in self.arcs_executed_set
|
|
137
|
-
if a in lines or b in lines
|
|
138
|
-
}
|
|
139
|
-
exit_counts = {
|
|
140
|
-
lno: num for lno, num in self.exit_counts.items()
|
|
141
|
-
if lno in lines
|
|
142
|
-
}
|
|
143
|
-
no_branch = {lno for lno in self.no_branch if lno in lines}
|
|
144
|
-
else:
|
|
145
|
-
arc_possibilities_set = set()
|
|
146
|
-
arcs_executed_set = set()
|
|
147
|
-
exit_counts = {}
|
|
148
|
-
no_branch = set()
|
|
149
|
-
|
|
150
|
-
return Analysis(
|
|
151
|
-
precision=self.precision,
|
|
152
|
-
filename=self.filename,
|
|
153
|
-
has_arcs=self.has_arcs,
|
|
154
|
-
statements=statements,
|
|
155
|
-
excluded=excluded,
|
|
156
|
-
executed=executed,
|
|
157
|
-
arc_possibilities_set=arc_possibilities_set,
|
|
158
|
-
arcs_executed_set=arcs_executed_set,
|
|
159
|
-
exit_counts=exit_counts,
|
|
160
|
-
no_branch=no_branch,
|
|
161
|
-
)
|
|
162
|
-
|
|
163
116
|
def missing_formatted(self, branches: bool = False) -> str:
|
|
164
117
|
"""The missing line numbers, formatted nicely.
|
|
165
118
|
|
|
@@ -178,16 +131,17 @@ class Analysis:
|
|
|
178
131
|
def arcs_missing(self) -> list[TArc]:
|
|
179
132
|
"""Returns a sorted list of the un-executed arcs in the code."""
|
|
180
133
|
missing = (
|
|
181
|
-
p
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
134
|
+
p
|
|
135
|
+
for p in self.arc_possibilities
|
|
136
|
+
if p not in self.arcs_executed_set
|
|
137
|
+
and p[0] not in self.no_branch
|
|
138
|
+
and p[1] not in self.excluded
|
|
185
139
|
)
|
|
186
140
|
return sorted(missing)
|
|
187
141
|
|
|
188
142
|
def _branch_lines(self) -> list[TLineNo]:
|
|
189
143
|
"""Returns a list of line numbers that have more than one exit."""
|
|
190
|
-
return [l1 for l1,count in self.exit_counts.items() if count > 1]
|
|
144
|
+
return [l1 for l1, count in self.exit_counts.items() if count > 1]
|
|
191
145
|
|
|
192
146
|
def _total_branches(self) -> int:
|
|
193
147
|
"""How many total branches are there?"""
|
|
@@ -231,6 +185,7 @@ class Analysis:
|
|
|
231
185
|
|
|
232
186
|
Returns a dict mapping line numbers to a tuple:
|
|
233
187
|
(total_exits, taken_exits).
|
|
188
|
+
|
|
234
189
|
"""
|
|
235
190
|
|
|
236
191
|
missing_arcs = self.missing_branch_arcs()
|
|
@@ -242,6 +197,104 @@ class Analysis:
|
|
|
242
197
|
return stats
|
|
243
198
|
|
|
244
199
|
|
|
200
|
+
TRegionLines = frozenset[TLineNo]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class AnalysisNarrower:
|
|
204
|
+
"""
|
|
205
|
+
For reducing an `Analysis` to a subset of its lines.
|
|
206
|
+
|
|
207
|
+
Originally this was a simpler method on Analysis, but that led to quadratic
|
|
208
|
+
behavior. This class does the bulk of the work up-front to provide the
|
|
209
|
+
same results in linear time.
|
|
210
|
+
|
|
211
|
+
Create an AnalysisNarrower from an Analysis, bulk-add region lines to it
|
|
212
|
+
with `add_regions`, then individually request new narrowed Analysis objects
|
|
213
|
+
for each region with `narrow`. Doing most of the work in limited calls to
|
|
214
|
+
`add_regions` lets us avoid poor performance.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
# In this class, regions are represented by a frozenset of their lines.
|
|
218
|
+
|
|
219
|
+
def __init__(self, analysis: Analysis) -> None:
|
|
220
|
+
self.analysis = analysis
|
|
221
|
+
self.region2arc_possibilities: dict[TRegionLines, set[TArc]] = collections.defaultdict(set)
|
|
222
|
+
self.region2arc_executed: dict[TRegionLines, set[TArc]] = collections.defaultdict(set)
|
|
223
|
+
self.region2exit_counts: dict[TRegionLines, dict[TLineNo, int]] = collections.defaultdict(
|
|
224
|
+
dict
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def add_regions(self, liness: Iterable[set[TLineNo]]) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Pre-process a number of sets of line numbers. Later calls to `narrow`
|
|
230
|
+
with one of these sets will provide a narrowed Analysis.
|
|
231
|
+
"""
|
|
232
|
+
if self.analysis.has_arcs:
|
|
233
|
+
line2region: dict[TLineNo, TRegionLines] = {}
|
|
234
|
+
|
|
235
|
+
for lines in liness:
|
|
236
|
+
fzlines = frozenset(lines)
|
|
237
|
+
for line in lines:
|
|
238
|
+
line2region[line] = fzlines
|
|
239
|
+
|
|
240
|
+
def collect_arcs(
|
|
241
|
+
arc_set: set[TArc],
|
|
242
|
+
region2arcs: dict[TRegionLines, set[TArc]],
|
|
243
|
+
) -> None:
|
|
244
|
+
for a, b in arc_set:
|
|
245
|
+
if r := line2region.get(a):
|
|
246
|
+
region2arcs[r].add((a, b))
|
|
247
|
+
if r := line2region.get(b):
|
|
248
|
+
region2arcs[r].add((a, b))
|
|
249
|
+
|
|
250
|
+
collect_arcs(self.analysis.arc_possibilities_set, self.region2arc_possibilities)
|
|
251
|
+
collect_arcs(self.analysis.arcs_executed_set, self.region2arc_executed)
|
|
252
|
+
|
|
253
|
+
for lno, num in self.analysis.exit_counts.items():
|
|
254
|
+
if r := line2region.get(lno):
|
|
255
|
+
self.region2exit_counts[r][lno] = num
|
|
256
|
+
|
|
257
|
+
def narrow(self, lines: set[TLineNo]) -> Analysis:
|
|
258
|
+
"""Create a narrowed Analysis.
|
|
259
|
+
|
|
260
|
+
The current analysis is copied to make a new one that only considers
|
|
261
|
+
the lines in `lines`.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
# Technically, the set intersections in this method are still O(N**2)
|
|
265
|
+
# since this method is called N times, but they're very fast and moving
|
|
266
|
+
# them to `add_regions` won't avoid the quadratic time.
|
|
267
|
+
|
|
268
|
+
statements = self.analysis.statements & lines
|
|
269
|
+
excluded = self.analysis.excluded & lines
|
|
270
|
+
executed = self.analysis.executed & lines
|
|
271
|
+
|
|
272
|
+
if self.analysis.has_arcs:
|
|
273
|
+
fzlines = frozenset(lines)
|
|
274
|
+
arc_possibilities_set = self.region2arc_possibilities[fzlines]
|
|
275
|
+
arcs_executed_set = self.region2arc_executed[fzlines]
|
|
276
|
+
exit_counts = self.region2exit_counts[fzlines]
|
|
277
|
+
no_branch = self.analysis.no_branch & lines
|
|
278
|
+
else:
|
|
279
|
+
arc_possibilities_set = set()
|
|
280
|
+
arcs_executed_set = set()
|
|
281
|
+
exit_counts = {}
|
|
282
|
+
no_branch = set()
|
|
283
|
+
|
|
284
|
+
return Analysis(
|
|
285
|
+
precision=self.analysis.precision,
|
|
286
|
+
filename=self.analysis.filename,
|
|
287
|
+
has_arcs=self.analysis.has_arcs,
|
|
288
|
+
statements=statements,
|
|
289
|
+
excluded=excluded,
|
|
290
|
+
executed=executed,
|
|
291
|
+
arc_possibilities_set=arc_possibilities_set,
|
|
292
|
+
arcs_executed_set=arcs_executed_set,
|
|
293
|
+
exit_counts=exit_counts,
|
|
294
|
+
no_branch=no_branch,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
245
298
|
@dataclasses.dataclass
|
|
246
299
|
class Numbers:
|
|
247
300
|
"""The numerical results of measuring coverage.
|
|
@@ -270,15 +323,36 @@ class Numbers:
|
|
|
270
323
|
"""Returns the number of executed branches."""
|
|
271
324
|
return self.n_branches - self.n_missing_branches
|
|
272
325
|
|
|
326
|
+
@property
|
|
327
|
+
def ratio_statements(self) -> tuple[int, int]:
|
|
328
|
+
"""Return numerator/denominator for statement coverage."""
|
|
329
|
+
return self.n_executed, self.n_statements
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def ratio_branches(self) -> tuple[int, int]:
|
|
333
|
+
"""Return numerator/denominator for branch coverage."""
|
|
334
|
+
return self.n_executed_branches, self.n_branches
|
|
335
|
+
|
|
336
|
+
def _percent(self, numerator: int, denominator: int) -> float:
|
|
337
|
+
"""Helper for pc_* properties."""
|
|
338
|
+
if denominator > 0:
|
|
339
|
+
return (100.0 * numerator) / denominator
|
|
340
|
+
return 100.0
|
|
341
|
+
|
|
273
342
|
@property
|
|
274
343
|
def pc_covered(self) -> float:
|
|
275
344
|
"""Returns a single percentage value for coverage."""
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return
|
|
345
|
+
return self._percent(*self.ratio_covered)
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def pc_statements(self) -> float:
|
|
349
|
+
"""Returns the percentage covered for statements."""
|
|
350
|
+
return self._percent(*self.ratio_statements)
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def pc_branches(self) -> float:
|
|
354
|
+
"""Returns the percentage covered for branches."""
|
|
355
|
+
return self._percent(*self.ratio_branches)
|
|
282
356
|
|
|
283
357
|
@property
|
|
284
358
|
def pc_covered_str(self) -> str:
|
|
@@ -291,6 +365,16 @@ class Numbers:
|
|
|
291
365
|
"""
|
|
292
366
|
return display_covered(self.pc_covered, self.precision)
|
|
293
367
|
|
|
368
|
+
@property
|
|
369
|
+
def pc_statements_str(self) -> str:
|
|
370
|
+
"""Returns the statement percent covered without a percent sign."""
|
|
371
|
+
return display_covered(self.pc_statements, self.precision)
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def pc_branches_str(self) -> str:
|
|
375
|
+
"""Returns the branch percent covered without a percent sign."""
|
|
376
|
+
return display_covered(self.pc_branches, self.precision)
|
|
377
|
+
|
|
294
378
|
@property
|
|
295
379
|
def ratio_covered(self) -> tuple[int, int]:
|
|
296
380
|
"""Return a numerator and denominator for the coverage ratio."""
|
|
@@ -312,7 +396,7 @@ class Numbers:
|
|
|
312
396
|
|
|
313
397
|
def __radd__(self, other: int) -> Numbers:
|
|
314
398
|
# Implementing 0+Numbers allows us to sum() a list of Numbers.
|
|
315
|
-
assert other == 0
|
|
399
|
+
assert other == 0 # we only ever call it this way.
|
|
316
400
|
return self
|
|
317
401
|
|
|
318
402
|
|
|
@@ -324,14 +408,14 @@ def display_covered(pc: float, precision: int) -> str:
|
|
|
324
408
|
result in either "0" or "100".
|
|
325
409
|
|
|
326
410
|
"""
|
|
327
|
-
near0 = 1.0 / 10
|
|
411
|
+
near0 = 1.0 / 10**precision
|
|
328
412
|
if 0 < pc < near0:
|
|
329
413
|
pc = near0
|
|
330
414
|
elif (100.0 - near0) < pc < 100:
|
|
331
415
|
pc = 100.0 - near0
|
|
332
416
|
else:
|
|
333
417
|
pc = round(pc, precision)
|
|
334
|
-
return "
|
|
418
|
+
return f"{pc:.{precision}f}"
|
|
335
419
|
|
|
336
420
|
|
|
337
421
|
def _line_ranges(
|
|
@@ -343,7 +427,7 @@ def _line_ranges(
|
|
|
343
427
|
lines = sorted(lines)
|
|
344
428
|
|
|
345
429
|
pairs = []
|
|
346
|
-
start = None
|
|
430
|
+
start: TLineNo | None = None
|
|
347
431
|
lidx = 0
|
|
348
432
|
for stmt in statements:
|
|
349
433
|
if lidx >= len(lines):
|
|
@@ -389,7 +473,7 @@ def format_lines(
|
|
|
389
473
|
for line, exits in line_exits:
|
|
390
474
|
for ex in sorted(exits):
|
|
391
475
|
if line not in lines and ex not in lines:
|
|
392
|
-
dest =
|
|
476
|
+
dest = ex if ex > 0 else "exit"
|
|
393
477
|
line_items.append((line, f"{line}->{dest}"))
|
|
394
478
|
|
|
395
479
|
ret = ", ".join(t[-1] for t in sorted(line_items))
|