coverage 7.11.1__cp314-cp314-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.
Files changed (59) hide show
  1. coverage/__init__.py +40 -0
  2. coverage/__main__.py +12 -0
  3. coverage/annotate.py +114 -0
  4. coverage/bytecode.py +196 -0
  5. coverage/cmdline.py +1184 -0
  6. coverage/collector.py +486 -0
  7. coverage/config.py +731 -0
  8. coverage/context.py +74 -0
  9. coverage/control.py +1481 -0
  10. coverage/core.py +139 -0
  11. coverage/data.py +227 -0
  12. coverage/debug.py +669 -0
  13. coverage/disposition.py +59 -0
  14. coverage/env.py +135 -0
  15. coverage/exceptions.py +91 -0
  16. coverage/execfile.py +329 -0
  17. coverage/files.py +553 -0
  18. coverage/html.py +856 -0
  19. coverage/htmlfiles/coverage_html.js +733 -0
  20. coverage/htmlfiles/favicon_32.png +0 -0
  21. coverage/htmlfiles/index.html +164 -0
  22. coverage/htmlfiles/keybd_closed.png +0 -0
  23. coverage/htmlfiles/pyfile.html +149 -0
  24. coverage/htmlfiles/style.css +377 -0
  25. coverage/htmlfiles/style.scss +824 -0
  26. coverage/inorout.py +614 -0
  27. coverage/jsonreport.py +188 -0
  28. coverage/lcovreport.py +219 -0
  29. coverage/misc.py +373 -0
  30. coverage/multiproc.py +120 -0
  31. coverage/numbits.py +146 -0
  32. coverage/parser.py +1213 -0
  33. coverage/patch.py +166 -0
  34. coverage/phystokens.py +197 -0
  35. coverage/plugin.py +617 -0
  36. coverage/plugin_support.py +299 -0
  37. coverage/py.typed +1 -0
  38. coverage/python.py +269 -0
  39. coverage/pytracer.py +369 -0
  40. coverage/regions.py +127 -0
  41. coverage/report.py +298 -0
  42. coverage/report_core.py +117 -0
  43. coverage/results.py +471 -0
  44. coverage/sqldata.py +1153 -0
  45. coverage/sqlitedb.py +239 -0
  46. coverage/sysmon.py +474 -0
  47. coverage/templite.py +306 -0
  48. coverage/tomlconfig.py +210 -0
  49. coverage/tracer.cpython-314-aarch64-linux-musl.so +0 -0
  50. coverage/tracer.pyi +43 -0
  51. coverage/types.py +206 -0
  52. coverage/version.py +35 -0
  53. coverage/xmlreport.py +264 -0
  54. coverage-7.11.1.dist-info/METADATA +221 -0
  55. coverage-7.11.1.dist-info/RECORD +59 -0
  56. coverage-7.11.1.dist-info/WHEEL +5 -0
  57. coverage-7.11.1.dist-info/entry_points.txt +4 -0
  58. coverage-7.11.1.dist-info/licenses/LICENSE.txt +177 -0
  59. coverage-7.11.1.dist-info/top_level.txt +1 -0
coverage/sysmon.py ADDED
@@ -0,0 +1,474 @@
1
+ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2
+ # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3
+
4
+ """Callback functions and support for sys.monitoring data collection."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import functools
9
+ import inspect
10
+ import os
11
+ import os.path
12
+ import sys
13
+ import threading
14
+ import traceback
15
+ from dataclasses import dataclass
16
+ from types import CodeType
17
+ from typing import Any, Callable, NewType, Optional, cast
18
+
19
+ from coverage import env
20
+ from coverage.bytecode import TBranchTrails, always_jumps, branch_trails
21
+ from coverage.debug import short_filename, short_stack
22
+ from coverage.misc import isolate_module
23
+ from coverage.parser import PythonParser
24
+ from coverage.types import (
25
+ AnyCallable,
26
+ TFileDisposition,
27
+ TLineNo,
28
+ TOffset,
29
+ Tracer,
30
+ TShouldStartContextFn,
31
+ TShouldTraceFn,
32
+ TTraceData,
33
+ TTraceFileData,
34
+ TWarnFn,
35
+ )
36
+
37
+ # Only needed for some of the commented-out logging:
38
+ # from coverage.debug import ppformat
39
+
40
+ os = isolate_module(os)
41
+
42
+ # pylint: disable=unused-argument
43
+
44
+ # $set_env.py: COVERAGE_SYSMON_LOG - Log sys.monitoring activity
45
+ LOG = bool(int(os.getenv("COVERAGE_SYSMON_LOG", 0)))
46
+
47
+ # $set_env.py: COVERAGE_SYSMON_STATS - Collect sys.monitoring stats
48
+ COLLECT_STATS = bool(int(os.getenv("COVERAGE_SYSMON_STATS", 0)))
49
+
50
+ # This module will be imported in all versions of Python, but only used in 3.12+
51
+ # It will be type-checked for 3.12, but not for earlier versions.
52
+ sys_monitoring = getattr(sys, "monitoring", None)
53
+
54
+ DISABLE_TYPE = NewType("DISABLE_TYPE", object)
55
+ MonitorReturn = Optional[DISABLE_TYPE]
56
+ DISABLE = cast(MonitorReturn, getattr(sys_monitoring, "DISABLE", None))
57
+
58
+
59
+ if LOG: # pragma: debugging
60
+
61
+ class LoggingWrapper:
62
+ """Wrap a namespace to log all its functions."""
63
+
64
+ def __init__(self, wrapped: Any, namespace: str) -> None:
65
+ self.wrapped = wrapped
66
+ self.namespace = namespace
67
+
68
+ def __getattr__(self, name: str) -> Callable[..., Any]:
69
+ def _wrapped(*args: Any, **kwargs: Any) -> Any:
70
+ log(f"{self.namespace}.{name}{args}{kwargs}")
71
+ return getattr(self.wrapped, name)(*args, **kwargs)
72
+
73
+ return _wrapped
74
+
75
+ sys_monitoring = LoggingWrapper(sys_monitoring, "sys.monitoring")
76
+ assert sys_monitoring is not None
77
+
78
+ short_stack = functools.partial(
79
+ short_stack,
80
+ full=True,
81
+ short_filenames=True,
82
+ frame_ids=True,
83
+ )
84
+ seen_threads: set[int] = set()
85
+
86
+ def log(msg: str) -> None:
87
+ """Write a message to our detailed debugging log(s)."""
88
+ # Thread ids are reused across processes?
89
+ # Make a shorter number more likely to be unique.
90
+ pid = os.getpid()
91
+ tid = cast(int, threading.current_thread().ident)
92
+ tslug = f"{(pid * tid) % 9_999_991:07d}"
93
+ if tid not in seen_threads:
94
+ seen_threads.add(tid)
95
+ log(f"New thread {tid} {tslug}:\n{short_stack()}")
96
+ # log_seq = int(os.getenv("PANSEQ", "0"))
97
+ # root = f"/tmp/pan.{log_seq:03d}"
98
+ for filename in [
99
+ "/tmp/foo.out",
100
+ # f"{root}.out",
101
+ # f"{root}-{pid}.out",
102
+ # f"{root}-{pid}-{tslug}.out",
103
+ ]:
104
+ with open(filename, "a", encoding="utf-8") as f:
105
+ try:
106
+ print(f"{pid}:{tslug}: {msg}", file=f, flush=True)
107
+ except UnicodeError:
108
+ print(f"{pid}:{tslug}: {ascii(msg)}", file=f, flush=True)
109
+
110
+ def arg_repr(arg: Any) -> str:
111
+ """Make a customized repr for logged values."""
112
+ if isinstance(arg, CodeType):
113
+ return (
114
+ f"<code @{id(arg):#x}"
115
+ + f" name={arg.co_name},"
116
+ + f" file={short_filename(arg.co_filename)!r}#{arg.co_firstlineno}>"
117
+ )
118
+ return repr(arg)
119
+
120
+ def panopticon(*names: str | None) -> AnyCallable:
121
+ """Decorate a function to log its calls."""
122
+
123
+ def _decorator(method: AnyCallable) -> AnyCallable:
124
+ @functools.wraps(method)
125
+ def _wrapped(self: Any, *args: Any) -> Any:
126
+ try:
127
+ # log(f"{method.__name__}() stack:\n{short_stack()}")
128
+ args_reprs = []
129
+ for name, arg in zip(names, args):
130
+ if name is None:
131
+ continue
132
+ args_reprs.append(f"{name}={arg_repr(arg)}")
133
+ log(f"{id(self):#x}:{method.__name__}({', '.join(args_reprs)})")
134
+ ret = method(self, *args)
135
+ # log(f" end {id(self):#x}:{method.__name__}({', '.join(args_reprs)})")
136
+ return ret
137
+ except Exception as exc:
138
+ log(f"!!{exc.__class__.__name__}: {exc}")
139
+ if 1:
140
+ log("".join(traceback.format_exception(exc)))
141
+ try:
142
+ assert sys_monitoring is not None
143
+ sys_monitoring.set_events(sys.monitoring.COVERAGE_ID, 0)
144
+ except ValueError:
145
+ # We might have already shut off monitoring.
146
+ log("oops, shutting off events with disabled tool id")
147
+ raise
148
+
149
+ return _wrapped
150
+
151
+ return _decorator
152
+
153
+ else:
154
+
155
+ def log(msg: str) -> None:
156
+ """Write a message to our detailed debugging log(s), but not really."""
157
+
158
+ def panopticon(*names: str | None) -> AnyCallable:
159
+ """Decorate a function to log its calls, but not really."""
160
+
161
+ def _decorator(meth: AnyCallable) -> AnyCallable:
162
+ return meth
163
+
164
+ return _decorator
165
+
166
+
167
+ @dataclass
168
+ class CodeInfo:
169
+ """The information we want about each code object."""
170
+
171
+ tracing: bool
172
+ file_data: TTraceFileData | None
173
+ byte_to_line: dict[TOffset, TLineNo] | None
174
+
175
+ # Keys are start instruction offsets for branches.
176
+ # Values are dicts:
177
+ # {
178
+ # (from_line, to_line): {offset, offset, ...},
179
+ # (from_line, to_line): {offset, offset, ...},
180
+ # }
181
+ branch_trails: TBranchTrails
182
+
183
+ # Always-jumps are bytecode offsets that do no work but move
184
+ # to another offset.
185
+ always_jumps: dict[TOffset, TOffset]
186
+
187
+
188
+ def bytes_to_lines(code: CodeType) -> dict[TOffset, TLineNo]:
189
+ """Make a dict mapping byte code offsets to line numbers."""
190
+ b2l = {}
191
+ for bstart, bend, lineno in code.co_lines():
192
+ if lineno is not None:
193
+ for boffset in range(bstart, bend, 2):
194
+ b2l[boffset] = lineno
195
+ return b2l
196
+
197
+
198
+ class SysMonitor(Tracer):
199
+ """Python implementation of the raw data tracer for PEP669 implementations."""
200
+
201
+ # One of these will be used across threads. Be careful.
202
+
203
+ def __init__(self, tool_id: int) -> None:
204
+ # Attributes set from the collector:
205
+ self.data: TTraceData
206
+ self.trace_arcs = False
207
+ self.should_trace: TShouldTraceFn
208
+ self.should_trace_cache: dict[str, TFileDisposition | None]
209
+ # TODO: should_start_context and switch_context are unused!
210
+ # Change tests/testenv.py:DYN_CONTEXTS when this is updated.
211
+ self.should_start_context: TShouldStartContextFn | None = None
212
+ self.switch_context: Callable[[str | None], None] | None = None
213
+ self.lock_data: Callable[[], None]
214
+ self.unlock_data: Callable[[], None]
215
+ # TODO: warn is unused.
216
+ self.warn: TWarnFn
217
+
218
+ self.myid = tool_id
219
+
220
+ # Map id(code_object) -> CodeInfo
221
+ self.code_infos: dict[int, CodeInfo] = {}
222
+ # A list of code_objects, just to keep them alive so that id's are
223
+ # useful as identity.
224
+ self.code_objects: list[CodeType] = []
225
+ self.sysmon_on = False
226
+ self.lock = threading.Lock()
227
+
228
+ self.stats: dict[str, int] | None = None
229
+ if COLLECT_STATS:
230
+ self.stats = dict.fromkeys(
231
+ "starts start_tracing returns line_lines line_arcs branches branch_trails".split(),
232
+ 0,
233
+ )
234
+
235
+ self._activity = False
236
+
237
+ def __repr__(self) -> str:
238
+ points = sum(len(v) for v in self.data.values())
239
+ files = len(self.data)
240
+ return f"<SysMonitor at {id(self):#x}: {points} data points in {files} files>"
241
+
242
+ @panopticon()
243
+ def start(self) -> None:
244
+ """Start this Tracer."""
245
+ with self.lock:
246
+ assert sys_monitoring is not None
247
+ sys_monitoring.use_tool_id(self.myid, "coverage.py")
248
+ register = functools.partial(sys_monitoring.register_callback, self.myid)
249
+ events = sys.monitoring.events
250
+
251
+ sys_monitoring.set_events(self.myid, events.PY_START)
252
+ register(events.PY_START, self.sysmon_py_start)
253
+ if self.trace_arcs:
254
+ register(events.PY_RETURN, self.sysmon_py_return)
255
+ register(events.LINE, self.sysmon_line_arcs)
256
+ if env.PYBEHAVIOR.branch_right_left:
257
+ register(
258
+ events.BRANCH_RIGHT, # type:ignore[attr-defined]
259
+ self.sysmon_branch_either,
260
+ )
261
+ register(
262
+ events.BRANCH_LEFT, # type:ignore[attr-defined]
263
+ self.sysmon_branch_either,
264
+ )
265
+ else:
266
+ register(events.LINE, self.sysmon_line_lines)
267
+ sys_monitoring.restart_events()
268
+ self.sysmon_on = True
269
+
270
+ @panopticon()
271
+ def stop(self) -> None:
272
+ """Stop this Tracer."""
273
+ with self.lock:
274
+ if not self.sysmon_on:
275
+ # In forking situations, we might try to stop when we are not
276
+ # started. Do nothing in that case.
277
+ return
278
+ assert sys_monitoring is not None
279
+ sys_monitoring.set_events(self.myid, 0)
280
+ self.sysmon_on = False
281
+ sys_monitoring.free_tool_id(self.myid)
282
+
283
+ @panopticon()
284
+ def post_fork(self) -> None:
285
+ """The process has forked, clean up as needed."""
286
+ self.stop()
287
+
288
+ def activity(self) -> bool:
289
+ """Has there been any activity?"""
290
+ return self._activity
291
+
292
+ def reset_activity(self) -> None:
293
+ """Reset the activity() flag."""
294
+ self._activity = False
295
+
296
+ def get_stats(self) -> dict[str, int] | None:
297
+ """Return a dictionary of statistics, or None."""
298
+ return self.stats
299
+
300
+ @panopticon("code", "@")
301
+ def sysmon_py_start(self, code: CodeType, instruction_offset: TOffset) -> MonitorReturn:
302
+ """Handle sys.monitoring.events.PY_START events."""
303
+ # Entering a new frame. Decide if we should trace in this file.
304
+ self._activity = True
305
+ if self.stats is not None:
306
+ self.stats["starts"] += 1
307
+
308
+ code_info = self.code_infos.get(id(code))
309
+ tracing_code: bool | None = None
310
+ file_data: TTraceFileData | None = None
311
+ if code_info is not None:
312
+ tracing_code = code_info.tracing
313
+ file_data = code_info.file_data
314
+
315
+ if tracing_code is None:
316
+ filename = code.co_filename
317
+ disp = self.should_trace_cache.get(filename)
318
+ if disp is None:
319
+ frame = inspect.currentframe()
320
+ if frame is not None:
321
+ frame = inspect.currentframe().f_back # type: ignore[union-attr]
322
+ if LOG:
323
+ # @panopticon adds a frame.
324
+ frame = frame.f_back # type: ignore[union-attr]
325
+ disp = self.should_trace(filename, frame) # type: ignore[arg-type]
326
+ self.should_trace_cache[filename] = disp
327
+
328
+ tracing_code = disp.trace
329
+ if tracing_code:
330
+ tracename = disp.source_filename
331
+ assert tracename is not None
332
+ self.lock_data()
333
+ try:
334
+ if tracename not in self.data:
335
+ self.data[tracename] = set()
336
+ finally:
337
+ self.unlock_data()
338
+ file_data = self.data[tracename]
339
+ b2l = bytes_to_lines(code)
340
+ else:
341
+ file_data = None
342
+ b2l = None
343
+
344
+ code_info = CodeInfo(
345
+ tracing=tracing_code,
346
+ file_data=file_data,
347
+ byte_to_line=b2l,
348
+ branch_trails={},
349
+ always_jumps={},
350
+ )
351
+ self.code_infos[id(code)] = code_info
352
+ self.code_objects.append(code)
353
+
354
+ if tracing_code:
355
+ if self.stats is not None:
356
+ self.stats["start_tracing"] += 1
357
+ events = sys.monitoring.events
358
+ with self.lock:
359
+ if self.sysmon_on:
360
+ assert sys_monitoring is not None
361
+ local_events = events.PY_RETURN | events.PY_RESUME | events.LINE
362
+ if self.trace_arcs:
363
+ assert env.PYBEHAVIOR.branch_right_left
364
+ local_events |= (
365
+ events.BRANCH_RIGHT # type:ignore[attr-defined]
366
+ | events.BRANCH_LEFT # type:ignore[attr-defined]
367
+ )
368
+ sys_monitoring.set_local_events(self.myid, code, local_events)
369
+
370
+ return DISABLE
371
+
372
+ @panopticon("code", "@", None)
373
+ def sysmon_py_return(
374
+ self,
375
+ code: CodeType,
376
+ instruction_offset: TOffset,
377
+ retval: object,
378
+ ) -> MonitorReturn:
379
+ """Handle sys.monitoring.events.PY_RETURN events for branch coverage."""
380
+ if self.stats is not None:
381
+ self.stats["returns"] += 1
382
+ code_info = self.code_infos.get(id(code))
383
+ # code_info is not None and code_info.file_data is not None, since we
384
+ # wouldn't have enabled this event if they were.
385
+ last_line = code_info.byte_to_line[instruction_offset] # type: ignore
386
+ if last_line is not None:
387
+ arc = (last_line, -code.co_firstlineno)
388
+ code_info.file_data.add(arc) # type: ignore
389
+ # log(f"adding {arc=}")
390
+ return DISABLE
391
+
392
+ @panopticon("code", "line")
393
+ def sysmon_line_lines(self, code: CodeType, line_number: TLineNo) -> MonitorReturn:
394
+ """Handle sys.monitoring.events.LINE events for line coverage."""
395
+ if self.stats is not None:
396
+ self.stats["line_lines"] += 1
397
+ code_info = self.code_infos.get(id(code))
398
+ # It should be true that code_info is not None and code_info.file_data
399
+ # is not None, since we wouldn't have enabled this event if they were.
400
+ # But somehow code_info can be None here, so we have to check.
401
+ if code_info is not None and code_info.file_data is not None:
402
+ code_info.file_data.add(line_number) # type: ignore
403
+ # log(f"adding {line_number=}")
404
+ return DISABLE
405
+
406
+ @panopticon("code", "line")
407
+ def sysmon_line_arcs(self, code: CodeType, line_number: TLineNo) -> MonitorReturn:
408
+ """Handle sys.monitoring.events.LINE events for branch coverage."""
409
+ if self.stats is not None:
410
+ self.stats["line_arcs"] += 1
411
+ code_info = self.code_infos[id(code)]
412
+ # code_info is not None and code_info.file_data is not None, since we
413
+ # wouldn't have enabled this event if they were.
414
+ arc = (line_number, line_number)
415
+ code_info.file_data.add(arc) # type: ignore
416
+ # log(f"adding {arc=}")
417
+ return DISABLE
418
+
419
+ @panopticon("code", "@", "@")
420
+ def sysmon_branch_either(
421
+ self, code: CodeType, instruction_offset: TOffset, destination_offset: TOffset
422
+ ) -> MonitorReturn:
423
+ """Handle BRANCH_RIGHT and BRANCH_LEFT events."""
424
+ if self.stats is not None:
425
+ self.stats["branches"] += 1
426
+ code_info = self.code_infos[id(code)]
427
+ # code_info is not None and code_info.file_data is not None, since we
428
+ # wouldn't have enabled this event if they were.
429
+ if not code_info.branch_trails:
430
+ if self.stats is not None:
431
+ self.stats["branch_trails"] += 1
432
+ multiline_map = get_multiline_map(code.co_filename)
433
+ code_info.branch_trails = branch_trails(code, multiline_map=multiline_map)
434
+ code_info.always_jumps = always_jumps(code)
435
+ # log(f"branch_trails for {code}:\n{ppformat(code_info.branch_trails)}")
436
+ added_arc = False
437
+ dest_info = code_info.branch_trails.get(instruction_offset)
438
+
439
+ # Re-map the destination offset through always-jumps to deal with NOP etc.
440
+ dests = {destination_offset}
441
+ while (dest := code_info.always_jumps.get(destination_offset)) is not None:
442
+ destination_offset = dest
443
+ dests.add(destination_offset)
444
+
445
+ # log(f"dest_info = {ppformat(dest_info)}")
446
+ if dest_info is not None:
447
+ for arc, offsets in dest_info.items():
448
+ if arc is None:
449
+ continue
450
+ if dests & offsets:
451
+ code_info.file_data.add(arc) # type: ignore
452
+ # log(f"adding {arc=}")
453
+ added_arc = True
454
+ break
455
+
456
+ if not added_arc:
457
+ # This could be an exception jumping from line to line.
458
+ assert code_info.byte_to_line is not None
459
+ l1 = code_info.byte_to_line[instruction_offset]
460
+ l2 = code_info.byte_to_line.get(destination_offset)
461
+ if l2 is not None and l1 != l2:
462
+ arc = (l1, l2)
463
+ code_info.file_data.add(arc) # type: ignore
464
+ # log(f"adding unforeseen {arc=}")
465
+
466
+ return DISABLE
467
+
468
+
469
+ @functools.lru_cache(maxsize=5)
470
+ def get_multiline_map(filename: str) -> dict[TLineNo, TLineNo]:
471
+ """Get a PythonParser for the given filename, cached."""
472
+ parser = PythonParser(filename=filename)
473
+ parser.parse_source()
474
+ return parser.multiline_map