coverage 7.17.0__cp313-cp313-android_24_arm64_v8a.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.
- a1_coverage.pth +1 -0
- coverage/__init__.py +36 -0
- coverage/__main__.py +12 -0
- coverage/annotate.py +119 -0
- coverage/bytecode.py +214 -0
- coverage/cmdline.py +1208 -0
- coverage/collector.py +498 -0
- coverage/config.py +739 -0
- coverage/context.py +79 -0
- coverage/control.py +1522 -0
- coverage/core.py +141 -0
- coverage/data.py +270 -0
- coverage/debug.py +664 -0
- coverage/disposition.py +59 -0
- coverage/env.py +144 -0
- coverage/exceptions.py +69 -0
- coverage/execfile.py +334 -0
- coverage/files.py +585 -0
- coverage/html.py +866 -0
- coverage/htmlfiles/coverage_html.js +778 -0
- coverage/htmlfiles/favicon_32.png +0 -0
- coverage/htmlfiles/index.html +199 -0
- coverage/htmlfiles/keybd_closed.png +0 -0
- coverage/htmlfiles/pyfile.html +149 -0
- coverage/htmlfiles/style.css +391 -0
- coverage/htmlfiles/style.scss +848 -0
- coverage/inorout.py +651 -0
- coverage/jsonreport.py +202 -0
- coverage/lcovreport.py +244 -0
- coverage/misc.py +382 -0
- coverage/multiproc.py +120 -0
- coverage/numbits.py +166 -0
- coverage/parser.py +1262 -0
- coverage/patch.py +118 -0
- coverage/phystokens.py +211 -0
- coverage/plugin.py +616 -0
- coverage/plugin_support.py +295 -0
- coverage/pth_file.py +16 -0
- coverage/py.typed +1 -0
- coverage/python.py +271 -0
- coverage/pytracer.py +383 -0
- coverage/regions.py +135 -0
- coverage/report.py +312 -0
- coverage/report_core.py +126 -0
- coverage/results.py +505 -0
- coverage/sqldata.py +1260 -0
- coverage/sqlitedb.py +231 -0
- coverage/sysmon.py +503 -0
- coverage/templite.py +317 -0
- coverage/tomlconfig.py +196 -0
- coverage/tracer.cpython-313-aarch64-linux-android.so +0 -0
- coverage/tracer.pyi +43 -0
- coverage/types.py +210 -0
- coverage/version.py +35 -0
- coverage/xmlreport.py +278 -0
- coverage-7.17.0.dist-info/METADATA +196 -0
- coverage-7.17.0.dist-info/RECORD +62 -0
- coverage-7.17.0.dist-info/WHEEL +5 -0
- coverage-7.17.0.dist-info/entry_points.txt +4 -0
- coverage-7.17.0.dist-info/licenses/LICENSE.txt +177 -0
- coverage-7.17.0.dist-info/licenses/NOTICE.txt +14 -0
- coverage-7.17.0.dist-info/top_level.txt +1 -0
a1_coverage.pth
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import sys; exec('import os\n\nif os.getenv("COVERAGE_PROCESS_START") or os.getenv("COVERAGE_PROCESS_CONFIG"):\n try:\n import coverage\n except:\n pass\n else:\n coverage.process_startup(slug="pth")')
|
coverage/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Code coverage measurement for Python.
|
|
6
|
+
|
|
7
|
+
Ned Batchelder
|
|
8
|
+
https://coverage.readthedocs.io
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
# mypy's convention is that "import as" names are public from the module.
|
|
15
|
+
# We import names as themselves to indicate that. Pylint sees it as pointless,
|
|
16
|
+
# so disable its warning.
|
|
17
|
+
# pylint: disable=useless-import-alias
|
|
18
|
+
from coverage.control import (
|
|
19
|
+
Coverage as Coverage,
|
|
20
|
+
process_startup as process_startup,
|
|
21
|
+
)
|
|
22
|
+
from coverage.data import CoverageData as CoverageData
|
|
23
|
+
from coverage.exceptions import CoverageException as CoverageException
|
|
24
|
+
from coverage.plugin import (
|
|
25
|
+
CodeRegion as CodeRegion,
|
|
26
|
+
CoveragePlugin as CoveragePlugin,
|
|
27
|
+
FileReporter as FileReporter,
|
|
28
|
+
FileTracer as FileTracer,
|
|
29
|
+
)
|
|
30
|
+
from coverage.version import (
|
|
31
|
+
__version__ as __version__,
|
|
32
|
+
version_info as version_info,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Backward compatibility.
|
|
36
|
+
coverage = Coverage
|
coverage/__main__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""Coverage.py's main entry point."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from coverage.cmdline import main
|
|
11
|
+
|
|
12
|
+
sys.exit(main())
|
coverage/annotate.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""Source file annotation for coverage.py."""
|
|
5
|
+
|
|
6
|
+
# pragma: enforce 100% coverage
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from coverage.files import flat_rootname
|
|
15
|
+
from coverage.misc import ensure_dir, isolate_module
|
|
16
|
+
from coverage.plugin import FileReporter
|
|
17
|
+
from coverage.report_core import get_analysis_to_report
|
|
18
|
+
from coverage.results import Analysis
|
|
19
|
+
from coverage.types import TMorfs
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from coverage import Coverage
|
|
23
|
+
|
|
24
|
+
os = isolate_module(os)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AnnotateReporter:
|
|
28
|
+
"""Generate annotated source files showing line coverage.
|
|
29
|
+
|
|
30
|
+
This reporter creates annotated copies of the measured source files. Each
|
|
31
|
+
.py file is copied as a .py,cover file, with a left-hand margin annotating
|
|
32
|
+
each line::
|
|
33
|
+
|
|
34
|
+
> def h(x):
|
|
35
|
+
- if 0: #pragma: no cover
|
|
36
|
+
- pass
|
|
37
|
+
> if x == 1:
|
|
38
|
+
! a = 1
|
|
39
|
+
> else:
|
|
40
|
+
> a = 2
|
|
41
|
+
|
|
42
|
+
> h(2)
|
|
43
|
+
|
|
44
|
+
Executed lines use ">", lines not executed use "!", lines excluded from
|
|
45
|
+
consideration use "-".
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, coverage: Coverage) -> None:
|
|
50
|
+
self.coverage = coverage
|
|
51
|
+
self.config = self.coverage.config
|
|
52
|
+
self.directory: str | None = None
|
|
53
|
+
|
|
54
|
+
blank_re = re.compile(r"\s*(#|$)")
|
|
55
|
+
else_re = re.compile(r"\s*else\s*:\s*(#|$)")
|
|
56
|
+
|
|
57
|
+
def report(self, morfs: TMorfs, directory: str | None = None) -> None:
|
|
58
|
+
"""Run the report.
|
|
59
|
+
|
|
60
|
+
See `coverage.report()` for arguments.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
self.directory = directory
|
|
64
|
+
self.coverage.get_data()
|
|
65
|
+
for fr, analysis in get_analysis_to_report(self.coverage, morfs):
|
|
66
|
+
self.annotate_file(fr, analysis)
|
|
67
|
+
|
|
68
|
+
def annotate_file(self, fr: FileReporter, analysis: Analysis) -> None:
|
|
69
|
+
"""Annotate a single file.
|
|
70
|
+
|
|
71
|
+
`fr` is the FileReporter for the file to annotate.
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
statements = sorted(analysis.statements)
|
|
75
|
+
missing = sorted(analysis.missing)
|
|
76
|
+
excluded = sorted(analysis.excluded)
|
|
77
|
+
|
|
78
|
+
if self.directory:
|
|
79
|
+
ensure_dir(self.directory)
|
|
80
|
+
rel_fname = fr.relative_filename()
|
|
81
|
+
dest_file = os.path.join(self.directory, flat_rootname(rel_fname))
|
|
82
|
+
# flat_rootname turned the dot of the extension into an underscore.
|
|
83
|
+
# Put the original extension back, whatever it is: .py, .pyw, or
|
|
84
|
+
# something a plugin measures.
|
|
85
|
+
ext = os.path.splitext(rel_fname)[1]
|
|
86
|
+
dest_file = dest_file[: len(dest_file) - len(ext)] + ext
|
|
87
|
+
else:
|
|
88
|
+
dest_file = fr.filename
|
|
89
|
+
dest_file += ",cover"
|
|
90
|
+
|
|
91
|
+
with open(dest_file, "w", encoding="utf-8") as dest:
|
|
92
|
+
i = j = 0
|
|
93
|
+
covered = True
|
|
94
|
+
source = fr.source()
|
|
95
|
+
for lineno, line in enumerate(source.splitlines(True), start=1):
|
|
96
|
+
while i < len(statements) and statements[i] < lineno:
|
|
97
|
+
i += 1
|
|
98
|
+
while j < len(missing) and missing[j] < lineno:
|
|
99
|
+
j += 1
|
|
100
|
+
if i < len(statements) and statements[i] == lineno:
|
|
101
|
+
covered = j >= len(missing) or missing[j] > lineno
|
|
102
|
+
if self.blank_re.match(line):
|
|
103
|
+
dest.write(" ")
|
|
104
|
+
elif self.else_re.match(line):
|
|
105
|
+
# Special logic for lines containing only "else:".
|
|
106
|
+
if j >= len(missing):
|
|
107
|
+
dest.write("> ")
|
|
108
|
+
elif statements[i] == missing[j]:
|
|
109
|
+
dest.write("! ")
|
|
110
|
+
else:
|
|
111
|
+
dest.write("> ")
|
|
112
|
+
elif lineno in excluded:
|
|
113
|
+
dest.write("- ")
|
|
114
|
+
elif covered:
|
|
115
|
+
dest.write("> ")
|
|
116
|
+
else:
|
|
117
|
+
dest.write("! ")
|
|
118
|
+
|
|
119
|
+
dest.write(line)
|
coverage/bytecode.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""Bytecode analysis for coverage.py"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import dis
|
|
9
|
+
from collections.abc import Iterable, Mapping
|
|
10
|
+
from types import CodeType
|
|
11
|
+
|
|
12
|
+
from coverage.types import TArc, TLineNo, TOffset
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ByteParser:
|
|
16
|
+
"""Parse bytecode to understand the structure of code."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
*,
|
|
21
|
+
code: CodeType | None = None,
|
|
22
|
+
text: str | None = None,
|
|
23
|
+
filename: str | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
if code is None:
|
|
26
|
+
assert text is not None
|
|
27
|
+
code = compile(text, filename or "<string>", "exec", dont_inherit=True)
|
|
28
|
+
self.code = code
|
|
29
|
+
|
|
30
|
+
def _child_parsers(self) -> Iterable[ByteParser]:
|
|
31
|
+
"""Iterate over all the code objects nested within this one.
|
|
32
|
+
|
|
33
|
+
The iteration includes `self` as its first value.
|
|
34
|
+
|
|
35
|
+
We skip code objects named `__annotate__` since they are deferred
|
|
36
|
+
annotations that usually are never run. If there are errors in the
|
|
37
|
+
annotations, they will be caught by type checkers or other tools that
|
|
38
|
+
use annotations.
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
return (ByteParser(code=c) for c in self.code_objects() if c.co_name != "__annotate__")
|
|
42
|
+
|
|
43
|
+
def code_objects(self) -> Iterable[CodeType]:
|
|
44
|
+
"""Iterate over all the code objects in `code`."""
|
|
45
|
+
stack = [self.code]
|
|
46
|
+
while stack:
|
|
47
|
+
# We're going to return the code object on the stack, but first
|
|
48
|
+
# push its children for later returning.
|
|
49
|
+
code = stack.pop()
|
|
50
|
+
for c in code.co_consts:
|
|
51
|
+
if isinstance(c, CodeType):
|
|
52
|
+
stack.append(c)
|
|
53
|
+
yield code
|
|
54
|
+
|
|
55
|
+
def _line_numbers(self) -> Iterable[TLineNo]:
|
|
56
|
+
"""Yield the line numbers possible in this code object.
|
|
57
|
+
|
|
58
|
+
Uses co_lines() to produce a sequence: l0, l1, ...
|
|
59
|
+
"""
|
|
60
|
+
for _, _, line in self.code.co_lines():
|
|
61
|
+
if line:
|
|
62
|
+
yield line
|
|
63
|
+
|
|
64
|
+
def find_statements(self) -> Iterable[TLineNo]:
|
|
65
|
+
"""Find the statements in `self.code`.
|
|
66
|
+
|
|
67
|
+
Produce a sequence of line numbers that start statements. Recurses
|
|
68
|
+
into all code objects reachable from `self.code`.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
for bp in self._child_parsers():
|
|
72
|
+
# Get all of the lineno information from this code.
|
|
73
|
+
yield from bp._line_numbers()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def bytes_to_lines(code: CodeType) -> dict[TOffset, TLineNo]:
|
|
77
|
+
"""Make a dict mapping byte code offsets to line numbers."""
|
|
78
|
+
b2l = {}
|
|
79
|
+
for bstart, bend, lineno in code.co_lines():
|
|
80
|
+
if lineno is not None:
|
|
81
|
+
for boffset in range(bstart, bend, 2):
|
|
82
|
+
b2l[boffset] = lineno
|
|
83
|
+
return b2l
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def op_set(*op_names: str) -> set[int]:
|
|
87
|
+
"""Make a set of opcodes from instruction names.
|
|
88
|
+
|
|
89
|
+
The names might not exist in this version of Python, skip those if not.
|
|
90
|
+
"""
|
|
91
|
+
ops = {op for name in op_names if (op := dis.opmap.get(name))}
|
|
92
|
+
assert ops, f"At least one opcode must exist: {op_names}"
|
|
93
|
+
return ops
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Opcodes that are unconditional jumps elsewhere.
|
|
97
|
+
ALWAYS_JUMPS = op_set(
|
|
98
|
+
"JUMP_BACKWARD",
|
|
99
|
+
"JUMP_BACKWARD_NO_INTERRUPT",
|
|
100
|
+
"JUMP_FORWARD",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Opcodes that exit from a function.
|
|
104
|
+
RETURNS = op_set(
|
|
105
|
+
"RETURN_VALUE",
|
|
106
|
+
"RETURN_GENERATOR",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Opcodes that do not fall through. The bytecode after them belongs to
|
|
110
|
+
# another path (for example the swallowed-exception side of a with).
|
|
111
|
+
NO_FALL_THROUGH = op_set("RERAISE")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# CACHE doesn't exist in PyPy, but the branch resolver is only used
|
|
115
|
+
# by sysmond, so a placeholder value is fine.
|
|
116
|
+
_CACHE = dis.opmap.get("CACHE", -1)
|
|
117
|
+
_EXTENDED_ARG = dis.opmap["EXTENDED_ARG"]
|
|
118
|
+
|
|
119
|
+
# All opcodes with a jump target.
|
|
120
|
+
JUMPS = set(dis.hasjrel) | set(dis.hasjabs)
|
|
121
|
+
|
|
122
|
+
# Opcodes that jump backwards.
|
|
123
|
+
BACKWARD_JUMPS = {op for op in JUMPS if "JUMP_BACKWARD" in dis.opname[op]}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class BranchArcResolver:
|
|
127
|
+
"""Resolve branch events to line arcs, one (source, dest) pair at a time.
|
|
128
|
+
|
|
129
|
+
Branch events are one-shot (they are DISABLEd after firing), so each
|
|
130
|
+
(source offset, destination offset) pair is resolved at most a couple of
|
|
131
|
+
times per code object. Resolving pairs on demand is much cheaper than
|
|
132
|
+
precomputing trails for every branch in the code object, most of which
|
|
133
|
+
never fire. We walk the raw bytecode bytes so that we never need to
|
|
134
|
+
disassemble whole code objects with `dis`.
|
|
135
|
+
|
|
136
|
+
To resolve one pair:
|
|
137
|
+
starting from the destination, follow the trail of instructions (through
|
|
138
|
+
unconditional jumps) until we reach an instruction on a new source line
|
|
139
|
+
(giving us the arc), a return (an arc to leaving the code object), or
|
|
140
|
+
another branch possibility (no arc: that branch will produce its own
|
|
141
|
+
events).
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
code: CodeType,
|
|
148
|
+
byte_to_line: Mapping[TOffset, TLineNo],
|
|
149
|
+
multiline_map: Mapping[TLineNo, TLineNo],
|
|
150
|
+
) -> None:
|
|
151
|
+
self.code = code
|
|
152
|
+
# co_code re-copies the bytes on each access, so fetch it once.
|
|
153
|
+
self.co_code = code.co_code
|
|
154
|
+
self.byte_to_line = byte_to_line
|
|
155
|
+
self.multiline_map = multiline_map
|
|
156
|
+
|
|
157
|
+
def line_at(self, offset: TOffset) -> TLineNo | None:
|
|
158
|
+
"""The source line of the instruction at `offset`, de-multilined."""
|
|
159
|
+
line = self.byte_to_line.get(offset)
|
|
160
|
+
if line is not None:
|
|
161
|
+
line = self.multiline_map.get(line, line)
|
|
162
|
+
return line
|
|
163
|
+
|
|
164
|
+
def resolve(self, source: TOffset, dest: TOffset) -> TArc | None:
|
|
165
|
+
"""Turn a branch event's (source, dest) offsets into an arc, or None."""
|
|
166
|
+
from_line = self.line_at(source)
|
|
167
|
+
if from_line is None:
|
|
168
|
+
return None
|
|
169
|
+
co_code = self.co_code
|
|
170
|
+
max_offset = len(co_code)
|
|
171
|
+
byte_to_line = self.byte_to_line
|
|
172
|
+
multiline_map = self.multiline_map
|
|
173
|
+
offset = dest
|
|
174
|
+
ext_arg = 0
|
|
175
|
+
seen: set[TOffset] = set()
|
|
176
|
+
while 0 <= offset < max_offset and offset not in seen:
|
|
177
|
+
seen.add(offset)
|
|
178
|
+
op = co_code[offset]
|
|
179
|
+
if op == _CACHE:
|
|
180
|
+
offset += 2
|
|
181
|
+
continue
|
|
182
|
+
if op == _EXTENDED_ARG:
|
|
183
|
+
ext_arg = (ext_arg | co_code[offset + 1]) << 8
|
|
184
|
+
offset += 2
|
|
185
|
+
continue
|
|
186
|
+
line = byte_to_line.get(offset)
|
|
187
|
+
if line is not None:
|
|
188
|
+
line = multiline_map.get(line, line)
|
|
189
|
+
if line and line != from_line:
|
|
190
|
+
return (from_line, line)
|
|
191
|
+
if op in JUMPS:
|
|
192
|
+
if op in ALWAYS_JUMPS:
|
|
193
|
+
arg = ext_arg | co_code[offset + 1]
|
|
194
|
+
# Jump distances are measured from the end of the
|
|
195
|
+
# instruction's inline CACHE entries, which appear in
|
|
196
|
+
# co_code as CACHE opcodes immediately following it.
|
|
197
|
+
next_offset = offset + 2
|
|
198
|
+
while next_offset < max_offset and co_code[next_offset] == _CACHE:
|
|
199
|
+
next_offset += 2
|
|
200
|
+
if op in BACKWARD_JUMPS:
|
|
201
|
+
offset = next_offset - 2 * arg
|
|
202
|
+
else:
|
|
203
|
+
offset = next_offset + 2 * arg
|
|
204
|
+
ext_arg = 0
|
|
205
|
+
continue
|
|
206
|
+
# Another branch possibility: it will get its own events.
|
|
207
|
+
return None
|
|
208
|
+
if op in RETURNS:
|
|
209
|
+
return (from_line, -self.code.co_firstlineno)
|
|
210
|
+
if op in NO_FALL_THROUGH:
|
|
211
|
+
return None
|
|
212
|
+
ext_arg = 0
|
|
213
|
+
offset += 2
|
|
214
|
+
return None
|