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