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.
Files changed (54) hide show
  1. coverage/__init__.py +2 -0
  2. coverage/__main__.py +2 -0
  3. coverage/annotate.py +1 -2
  4. coverage/bytecode.py +177 -3
  5. coverage/cmdline.py +329 -154
  6. coverage/collector.py +31 -42
  7. coverage/config.py +166 -62
  8. coverage/context.py +4 -5
  9. coverage/control.py +164 -85
  10. coverage/core.py +70 -33
  11. coverage/data.py +3 -4
  12. coverage/debug.py +112 -56
  13. coverage/disposition.py +1 -0
  14. coverage/env.py +65 -55
  15. coverage/exceptions.py +35 -7
  16. coverage/execfile.py +18 -13
  17. coverage/files.py +23 -18
  18. coverage/html.py +134 -88
  19. coverage/htmlfiles/style.css +42 -2
  20. coverage/htmlfiles/style.scss +65 -1
  21. coverage/inorout.py +61 -44
  22. coverage/jsonreport.py +17 -8
  23. coverage/lcovreport.py +16 -20
  24. coverage/misc.py +50 -46
  25. coverage/multiproc.py +12 -7
  26. coverage/numbits.py +3 -4
  27. coverage/parser.py +193 -269
  28. coverage/patch.py +166 -0
  29. coverage/phystokens.py +24 -25
  30. coverage/plugin.py +13 -13
  31. coverage/plugin_support.py +36 -35
  32. coverage/python.py +9 -13
  33. coverage/pytracer.py +40 -33
  34. coverage/regions.py +2 -1
  35. coverage/report.py +59 -43
  36. coverage/report_core.py +6 -9
  37. coverage/results.py +118 -66
  38. coverage/sqldata.py +260 -210
  39. coverage/sqlitedb.py +33 -25
  40. coverage/sysmon.py +195 -157
  41. coverage/templite.py +6 -6
  42. coverage/tomlconfig.py +12 -12
  43. coverage/tracer.cp311-win_amd64.pyd +0 -0
  44. coverage/tracer.pyi +2 -0
  45. coverage/types.py +25 -22
  46. coverage/version.py +3 -18
  47. coverage/xmlreport.py +16 -13
  48. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/METADATA +40 -18
  49. coverage-7.11.1.dist-info/RECORD +59 -0
  50. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/WHEEL +1 -1
  51. coverage-7.6.7.dist-info/RECORD +0 -58
  52. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/entry_points.txt +0 -0
  53. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info/licenses}/LICENSE.txt +0 -0
  54. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/top_level.txt +0 -0
coverage/__init__.py CHANGED
@@ -11,6 +11,8 @@ https://coverage.readthedocs.io
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
+ # isort: skip_file
15
+
14
16
  # mypy's convention is that "import as" names are public from the module.
15
17
  # We import names as themselves to indicate that. Pylint sees it as pointless,
16
18
  # so disable its warning.
coverage/__main__.py CHANGED
@@ -6,5 +6,7 @@
6
6
  from __future__ import annotations
7
7
 
8
8
  import sys
9
+
9
10
  from coverage.cmdline import main
11
+
10
12
  sys.exit(main())
coverage/annotate.py CHANGED
@@ -7,9 +7,8 @@ from __future__ import annotations
7
7
 
8
8
  import os
9
9
  import re
10
-
11
- from typing import TYPE_CHECKING
12
10
  from collections.abc import Iterable
11
+ from typing import TYPE_CHECKING
13
12
 
14
13
  from coverage.files import flat_rootname
15
14
  from coverage.misc import ensure_dir, isolate_module
coverage/bytecode.py CHANGED
@@ -1,15 +1,19 @@
1
1
  # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2
2
  # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3
3
 
4
- """Bytecode manipulation for coverage.py"""
4
+ """Bytecode analysis for coverage.py"""
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import collections
9
+ import dis
8
10
  from types import CodeType
9
- from collections.abc import Iterator
11
+ from typing import Iterable, Mapping, Optional
10
12
 
13
+ from coverage.types import TArc, TLineNo, TOffset
11
14
 
12
- def code_objects(code: CodeType) -> Iterator[CodeType]:
15
+
16
+ def code_objects(code: CodeType) -> Iterable[CodeType]:
13
17
  """Iterate over all the code objects in `code`."""
14
18
  stack = [code]
15
19
  while stack:
@@ -20,3 +24,173 @@ def code_objects(code: CodeType) -> Iterator[CodeType]:
20
24
  if isinstance(c, CodeType):
21
25
  stack.append(c)
22
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