coverage 7.11.3__cp314-cp314t-win32.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 +40 -0
- coverage/__main__.py +12 -0
- coverage/annotate.py +114 -0
- coverage/bytecode.py +196 -0
- coverage/cmdline.py +1184 -0
- coverage/collector.py +486 -0
- coverage/config.py +731 -0
- coverage/context.py +74 -0
- coverage/control.py +1481 -0
- coverage/core.py +139 -0
- coverage/data.py +227 -0
- coverage/debug.py +669 -0
- coverage/disposition.py +59 -0
- coverage/env.py +135 -0
- coverage/exceptions.py +85 -0
- coverage/execfile.py +329 -0
- coverage/files.py +553 -0
- coverage/html.py +856 -0
- coverage/htmlfiles/coverage_html.js +733 -0
- coverage/htmlfiles/favicon_32.png +0 -0
- coverage/htmlfiles/index.html +164 -0
- coverage/htmlfiles/keybd_closed.png +0 -0
- coverage/htmlfiles/pyfile.html +149 -0
- coverage/htmlfiles/style.css +377 -0
- coverage/htmlfiles/style.scss +824 -0
- coverage/inorout.py +614 -0
- coverage/jsonreport.py +188 -0
- coverage/lcovreport.py +219 -0
- coverage/misc.py +373 -0
- coverage/multiproc.py +120 -0
- coverage/numbits.py +146 -0
- coverage/parser.py +1213 -0
- coverage/patch.py +166 -0
- coverage/phystokens.py +197 -0
- coverage/plugin.py +617 -0
- coverage/plugin_support.py +299 -0
- coverage/py.typed +1 -0
- coverage/python.py +269 -0
- coverage/pytracer.py +369 -0
- coverage/regions.py +127 -0
- coverage/report.py +298 -0
- coverage/report_core.py +117 -0
- coverage/results.py +471 -0
- coverage/sqldata.py +1153 -0
- coverage/sqlitedb.py +239 -0
- coverage/sysmon.py +482 -0
- coverage/templite.py +306 -0
- coverage/tomlconfig.py +210 -0
- coverage/tracer.cp314t-win32.pyd +0 -0
- coverage/tracer.pyi +43 -0
- coverage/types.py +206 -0
- coverage/version.py +35 -0
- coverage/xmlreport.py +264 -0
- coverage-7.11.3.dist-info/METADATA +221 -0
- coverage-7.11.3.dist-info/RECORD +59 -0
- coverage-7.11.3.dist-info/WHEEL +5 -0
- coverage-7.11.3.dist-info/entry_points.txt +4 -0
- coverage-7.11.3.dist-info/licenses/LICENSE.txt +177 -0
- coverage-7.11.3.dist-info/top_level.txt +1 -0
coverage/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
# isort: skip_file
|
|
15
|
+
|
|
16
|
+
# mypy's convention is that "import as" names are public from the module.
|
|
17
|
+
# We import names as themselves to indicate that. Pylint sees it as pointless,
|
|
18
|
+
# so disable its warning.
|
|
19
|
+
# pylint: disable=useless-import-alias
|
|
20
|
+
|
|
21
|
+
from coverage.version import (
|
|
22
|
+
__version__ as __version__,
|
|
23
|
+
version_info as version_info,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from coverage.control import (
|
|
27
|
+
Coverage as Coverage,
|
|
28
|
+
process_startup as process_startup,
|
|
29
|
+
)
|
|
30
|
+
from coverage.data import CoverageData as CoverageData
|
|
31
|
+
from coverage.exceptions import CoverageException as CoverageException
|
|
32
|
+
from coverage.plugin import (
|
|
33
|
+
CodeRegion as CodeRegion,
|
|
34
|
+
CoveragePlugin as CoveragePlugin,
|
|
35
|
+
FileReporter as FileReporter,
|
|
36
|
+
FileTracer as FileTracer,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Backward compatibility.
|
|
40
|
+
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,114 @@
|
|
|
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
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from collections.abc import Iterable
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from coverage.files import flat_rootname
|
|
14
|
+
from coverage.misc import ensure_dir, isolate_module
|
|
15
|
+
from coverage.plugin import FileReporter
|
|
16
|
+
from coverage.report_core import get_analysis_to_report
|
|
17
|
+
from coverage.results import Analysis
|
|
18
|
+
from coverage.types import TMorf
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from coverage import Coverage
|
|
22
|
+
|
|
23
|
+
os = isolate_module(os)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AnnotateReporter:
|
|
27
|
+
"""Generate annotated source files showing line coverage.
|
|
28
|
+
|
|
29
|
+
This reporter creates annotated copies of the measured source files. Each
|
|
30
|
+
.py file is copied as a .py,cover file, with a left-hand margin annotating
|
|
31
|
+
each line::
|
|
32
|
+
|
|
33
|
+
> def h(x):
|
|
34
|
+
- if 0: #pragma: no cover
|
|
35
|
+
- pass
|
|
36
|
+
> if x == 1:
|
|
37
|
+
! a = 1
|
|
38
|
+
> else:
|
|
39
|
+
> a = 2
|
|
40
|
+
|
|
41
|
+
> h(2)
|
|
42
|
+
|
|
43
|
+
Executed lines use ">", lines not executed use "!", lines excluded from
|
|
44
|
+
consideration use "-".
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, coverage: Coverage) -> None:
|
|
49
|
+
self.coverage = coverage
|
|
50
|
+
self.config = self.coverage.config
|
|
51
|
+
self.directory: str | None = None
|
|
52
|
+
|
|
53
|
+
blank_re = re.compile(r"\s*(#|$)")
|
|
54
|
+
else_re = re.compile(r"\s*else\s*:\s*(#|$)")
|
|
55
|
+
|
|
56
|
+
def report(self, morfs: Iterable[TMorf] | None, directory: str | None = None) -> None:
|
|
57
|
+
"""Run the report.
|
|
58
|
+
|
|
59
|
+
See `coverage.report()` for arguments.
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
self.directory = directory
|
|
63
|
+
self.coverage.get_data()
|
|
64
|
+
for fr, analysis in get_analysis_to_report(self.coverage, morfs):
|
|
65
|
+
self.annotate_file(fr, analysis)
|
|
66
|
+
|
|
67
|
+
def annotate_file(self, fr: FileReporter, analysis: Analysis) -> None:
|
|
68
|
+
"""Annotate a single file.
|
|
69
|
+
|
|
70
|
+
`fr` is the FileReporter for the file to annotate.
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
statements = sorted(analysis.statements)
|
|
74
|
+
missing = sorted(analysis.missing)
|
|
75
|
+
excluded = sorted(analysis.excluded)
|
|
76
|
+
|
|
77
|
+
if self.directory:
|
|
78
|
+
ensure_dir(self.directory)
|
|
79
|
+
dest_file = os.path.join(self.directory, flat_rootname(fr.relative_filename()))
|
|
80
|
+
assert dest_file.endswith("_py")
|
|
81
|
+
dest_file = dest_file[:-3] + ".py"
|
|
82
|
+
else:
|
|
83
|
+
dest_file = fr.filename
|
|
84
|
+
dest_file += ",cover"
|
|
85
|
+
|
|
86
|
+
with open(dest_file, "w", encoding="utf-8") as dest:
|
|
87
|
+
i = j = 0
|
|
88
|
+
covered = True
|
|
89
|
+
source = fr.source()
|
|
90
|
+
for lineno, line in enumerate(source.splitlines(True), start=1):
|
|
91
|
+
while i < len(statements) and statements[i] < lineno:
|
|
92
|
+
i += 1
|
|
93
|
+
while j < len(missing) and missing[j] < lineno:
|
|
94
|
+
j += 1
|
|
95
|
+
if i < len(statements) and statements[i] == lineno:
|
|
96
|
+
covered = j >= len(missing) or missing[j] > lineno
|
|
97
|
+
if self.blank_re.match(line):
|
|
98
|
+
dest.write(" ")
|
|
99
|
+
elif self.else_re.match(line):
|
|
100
|
+
# Special logic for lines containing only "else:".
|
|
101
|
+
if j >= len(missing):
|
|
102
|
+
dest.write("> ")
|
|
103
|
+
elif statements[i] == missing[j]:
|
|
104
|
+
dest.write("! ")
|
|
105
|
+
else:
|
|
106
|
+
dest.write("> ")
|
|
107
|
+
elif lineno in excluded:
|
|
108
|
+
dest.write("- ")
|
|
109
|
+
elif covered:
|
|
110
|
+
dest.write("> ")
|
|
111
|
+
else:
|
|
112
|
+
dest.write("! ")
|
|
113
|
+
|
|
114
|
+
dest.write(line)
|
coverage/bytecode.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
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 collections
|
|
9
|
+
import dis
|
|
10
|
+
from types import CodeType
|
|
11
|
+
from typing import Iterable, Mapping, Optional
|
|
12
|
+
|
|
13
|
+
from coverage.types import TArc, TLineNo, TOffset
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def code_objects(code: CodeType) -> Iterable[CodeType]:
|
|
17
|
+
"""Iterate over all the code objects in `code`."""
|
|
18
|
+
stack = [code]
|
|
19
|
+
while stack:
|
|
20
|
+
# We're going to return the code object on the stack, but first
|
|
21
|
+
# push its children for later returning.
|
|
22
|
+
code = stack.pop()
|
|
23
|
+
for c in code.co_consts:
|
|
24
|
+
if isinstance(c, CodeType):
|
|
25
|
+
stack.append(c)
|
|
26
|
+
yield code
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def op_set(*op_names: str) -> set[int]:
|
|
30
|
+
"""Make a set of opcodes from instruction names.
|
|
31
|
+
|
|
32
|
+
The names might not exist in this version of Python, skip those if not.
|
|
33
|
+
"""
|
|
34
|
+
ops = {op for name in op_names if (op := dis.opmap.get(name))}
|
|
35
|
+
assert ops, f"At least one opcode must exist: {op_names}"
|
|
36
|
+
return ops
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Opcodes that are unconditional jumps elsewhere.
|
|
40
|
+
ALWAYS_JUMPS = op_set(
|
|
41
|
+
"JUMP_BACKWARD",
|
|
42
|
+
"JUMP_BACKWARD_NO_INTERRUPT",
|
|
43
|
+
"JUMP_FORWARD",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Opcodes that exit from a function.
|
|
47
|
+
RETURNS = op_set(
|
|
48
|
+
"RETURN_VALUE",
|
|
49
|
+
"RETURN_GENERATOR",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Opcodes that do nothing.
|
|
53
|
+
NOPS = op_set(
|
|
54
|
+
"NOP",
|
|
55
|
+
"NOT_TAKEN",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class InstructionWalker:
|
|
60
|
+
"""Utility to step through trails of instructions.
|
|
61
|
+
|
|
62
|
+
We have two reasons to need sequences of instructions from a code object:
|
|
63
|
+
First, in strict sequence to visit all the instructions in the object.
|
|
64
|
+
This is `walk(follow_jumps=False)`. Second, we want to follow jumps to
|
|
65
|
+
understand how execution will flow: `walk(follow_jumps=True)`.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, code: CodeType) -> None:
|
|
69
|
+
self.code = code
|
|
70
|
+
self.insts: dict[TOffset, dis.Instruction] = {}
|
|
71
|
+
|
|
72
|
+
inst = None
|
|
73
|
+
for inst in dis.get_instructions(code):
|
|
74
|
+
self.insts[inst.offset] = inst
|
|
75
|
+
|
|
76
|
+
assert inst is not None
|
|
77
|
+
self.max_offset = inst.offset
|
|
78
|
+
|
|
79
|
+
def walk(
|
|
80
|
+
self, *, start_at: TOffset = 0, follow_jumps: bool = True
|
|
81
|
+
) -> Iterable[dis.Instruction]:
|
|
82
|
+
"""
|
|
83
|
+
Yield instructions starting from `start_at`. Follow unconditional
|
|
84
|
+
jumps if `follow_jumps` is true.
|
|
85
|
+
"""
|
|
86
|
+
seen = set()
|
|
87
|
+
offset = start_at
|
|
88
|
+
while offset < self.max_offset + 1:
|
|
89
|
+
if offset in seen:
|
|
90
|
+
break
|
|
91
|
+
seen.add(offset)
|
|
92
|
+
if inst := self.insts.get(offset):
|
|
93
|
+
yield inst
|
|
94
|
+
if follow_jumps and inst.opcode in ALWAYS_JUMPS:
|
|
95
|
+
offset = inst.jump_target
|
|
96
|
+
continue
|
|
97
|
+
offset += 2
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
TBranchTrailsOneSource = dict[Optional[TArc], set[TOffset]]
|
|
101
|
+
TBranchTrails = dict[TOffset, TBranchTrailsOneSource]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def branch_trails(
|
|
105
|
+
code: CodeType,
|
|
106
|
+
multiline_map: Mapping[TLineNo, TLineNo],
|
|
107
|
+
) -> TBranchTrails:
|
|
108
|
+
"""
|
|
109
|
+
Calculate branch trails for `code`.
|
|
110
|
+
|
|
111
|
+
`multiline_map` maps line numbers to the first line number of a
|
|
112
|
+
multi-line statement.
|
|
113
|
+
|
|
114
|
+
Instructions can have a jump_target, where they might jump to next. Some
|
|
115
|
+
instructions with a jump_target are unconditional jumps (ALWAYS_JUMPS), so
|
|
116
|
+
they aren't interesting to us, since they aren't the start of a branch
|
|
117
|
+
possibility.
|
|
118
|
+
|
|
119
|
+
Instructions that might or might not jump somewhere else are branch
|
|
120
|
+
possibilities. For each of those, we track a trail of instructions. These
|
|
121
|
+
are lists of instruction offsets, the next instructions that can execute.
|
|
122
|
+
We follow the trail until we get to a new source line. That gives us the
|
|
123
|
+
arc from the original instruction's line to the new source line.
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
the_trails: TBranchTrails = collections.defaultdict(lambda: collections.defaultdict(set))
|
|
127
|
+
iwalker = InstructionWalker(code)
|
|
128
|
+
for inst in iwalker.walk(follow_jumps=False):
|
|
129
|
+
if not inst.jump_target:
|
|
130
|
+
# We only care about instructions with jump targets.
|
|
131
|
+
continue
|
|
132
|
+
if inst.opcode in ALWAYS_JUMPS:
|
|
133
|
+
# We don't care about unconditional jumps.
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
from_line = inst.line_number
|
|
137
|
+
if from_line is None:
|
|
138
|
+
continue
|
|
139
|
+
from_line = multiline_map.get(from_line, from_line)
|
|
140
|
+
|
|
141
|
+
def add_one_branch_trail(
|
|
142
|
+
trails: TBranchTrailsOneSource,
|
|
143
|
+
start_at: TOffset,
|
|
144
|
+
) -> None:
|
|
145
|
+
# pylint: disable=cell-var-from-loop
|
|
146
|
+
inst_offsets: set[TOffset] = set()
|
|
147
|
+
to_line = None
|
|
148
|
+
for inst2 in iwalker.walk(start_at=start_at, follow_jumps=True):
|
|
149
|
+
inst_offsets.add(inst2.offset)
|
|
150
|
+
l2 = inst2.line_number
|
|
151
|
+
if l2 is not None:
|
|
152
|
+
l2 = multiline_map.get(l2, l2)
|
|
153
|
+
if l2 and l2 != from_line:
|
|
154
|
+
to_line = l2
|
|
155
|
+
break
|
|
156
|
+
elif inst2.jump_target and (inst2.opcode not in ALWAYS_JUMPS):
|
|
157
|
+
break
|
|
158
|
+
elif inst2.opcode in RETURNS:
|
|
159
|
+
to_line = -code.co_firstlineno
|
|
160
|
+
break
|
|
161
|
+
if to_line is not None:
|
|
162
|
+
trails[(from_line, to_line)].update(inst_offsets)
|
|
163
|
+
else:
|
|
164
|
+
trails[None] = set()
|
|
165
|
+
|
|
166
|
+
# Calculate two trails: one from the next instruction, and one from the
|
|
167
|
+
# jump_target instruction.
|
|
168
|
+
trails: TBranchTrailsOneSource = collections.defaultdict(set)
|
|
169
|
+
add_one_branch_trail(trails, start_at=inst.offset + 2)
|
|
170
|
+
add_one_branch_trail(trails, start_at=inst.jump_target)
|
|
171
|
+
the_trails[inst.offset] = trails
|
|
172
|
+
|
|
173
|
+
# Sometimes we get BRANCH_RIGHT or BRANCH_LEFT events from instructions
|
|
174
|
+
# other than the original jump possibility instruction. Register each
|
|
175
|
+
# trail under all of their offsets so we can pick up in the middle of a
|
|
176
|
+
# trail if need be.
|
|
177
|
+
for arc, offsets in trails.items():
|
|
178
|
+
for offset in offsets:
|
|
179
|
+
the_trails[offset][arc].update(offsets)
|
|
180
|
+
|
|
181
|
+
return the_trails
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def always_jumps(code: CodeType) -> dict[TOffset, TOffset]:
|
|
185
|
+
"""Make a map of unconditional bytecodes jumping to others.
|
|
186
|
+
|
|
187
|
+
Only include bytecodes that do no work and go to another bytecode.
|
|
188
|
+
"""
|
|
189
|
+
jumps = {}
|
|
190
|
+
iwalker = InstructionWalker(code)
|
|
191
|
+
for inst in iwalker.walk(follow_jumps=False):
|
|
192
|
+
if inst.opcode in ALWAYS_JUMPS:
|
|
193
|
+
jumps[inst.offset] = inst.jump_target
|
|
194
|
+
elif inst.opcode in NOPS:
|
|
195
|
+
jumps[inst.offset] = inst.offset + 2
|
|
196
|
+
return jumps
|