Coverage for coverage / debug.py: 97.600%
277 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
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
4"""Control of and utilities for debugging."""
6from __future__ import annotations 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
8import _thread 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
9import atexit 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
10import contextlib 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
11import datetime 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
12import functools 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
13import inspect 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
14import itertools 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
15import os 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
16import pprint 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
17import re 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
18import reprlib 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
19import sys 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
20import traceback 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
21import types 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
22from collections.abc import Iterable, Iterator, Mapping 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
23from typing import IO, Any, Callable, Final, overload 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
25from coverage.misc import human_sorted_items, isolate_module 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
26from coverage.types import AnyCallable, TWritable 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
28os = isolate_module(os) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
31# When debugging, it can be helpful to force some options, especially when
32# debugging the configuration mechanisms you usually use to control debugging!
33# This is a list of forced debugging options.
34FORCED_DEBUG: list[str] = [] 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
35FORCED_DEBUG_FILE = None 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
38class DebugControl: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
39 """Control and output for debugging."""
41 show_repr_attr = False # For auto_repr 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
43 def __init__( 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234
44 self,
45 options: Iterable[str],
46 output: IO[str] | None,
47 file_name: str | None = None,
48 ) -> None:
49 """Configure the options and output file for debugging."""
50 self.options = list(options) + FORCED_DEBUG 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
51 self.suppress_callers = False 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
53 filters = [] 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
54 if self.should("process"): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
55 filters.append(CwdTracker().filter) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LMNO8PQ9RSTU!VW#XYZ0$12534EFG
56 filters.append(ProcessTracker().filter) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LMNO8PQ9RSTU!VW#XYZ0$12534EFG
57 if self.should("pytest"): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
58 filters.append(PytestTracker().filter) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
59 if self.should("pid"): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
60 filters.append(add_pid_and_tid) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LMNO8PQ9RSTU!VW#XYZ0$12534EFG
62 self.output = DebugOutputFile.get_one( 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
63 output,
64 file_name=file_name,
65 filters=filters,
66 )
67 self.raw_output = self.output.outfile 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
69 def __repr__(self) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
70 return f"<DebugControl options={self.options!r} raw_output={self.raw_output!r}>"
72 def should(self, option: str) -> bool: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
73 """Decide whether to output debug information in category `option`."""
74 if option == "callers" and self.suppress_callers: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
75 return False 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
76 return option in self.options 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
78 @contextlib.contextmanager 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
79 def without_callers(self) -> Iterator[None]: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
80 """A context manager to prevent call stacks from being logged."""
81 old = self.suppress_callers 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
82 self.suppress_callers = True 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
83 try: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
84 yield 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
85 finally:
86 self.suppress_callers = old 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
88 def write(self, msg: str, *, exc: BaseException | None = None) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
89 """Write a line of debug output.
91 `msg` is the line to write. A newline will be appended.
93 If `exc` is provided, a stack trace of the exception will be written
94 after the message.
96 """
97 self.output.write(msg + "\n") 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
98 if exc is not None: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
99 self.output.write("".join(traceback.format_exception(None, exc, exc.__traceback__))) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
100 if self.should("self"): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
101 caller_self = inspect.stack()[1][0].f_locals.get("self") 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
102 if caller_self is not None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
103 self.output.write(f"self: {caller_self!r}\n") 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
104 if self.should("callers"): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
105 dump_stack_frames(out=self.output, skip=1) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ012534EFG
106 self.output.flush() 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
109class NoDebugging(DebugControl): 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
110 """A replacement for DebugControl that will never try to do anything."""
112 def __init__(self) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
113 # pylint: disable=super-init-not-called
114 pass 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
116 def should(self, option: str) -> bool: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
117 """Should we write debug messages? Never."""
118 return False 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
120 @contextlib.contextmanager 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
121 def without_callers(self) -> Iterator[None]: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
122 """A dummy context manager to satisfy the api."""
123 yield # pragma: never called
125 def write(self, msg: str, *, exc: BaseException | None = None) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
126 """This will never be called."""
127 raise AssertionError("NoDebugging.write should never be called.")
130class DevNullDebug(NoDebugging): 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
131 """A DebugControl that won't write anywhere."""
133 def write(self, msg: str, *, exc: BaseException | None = None) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
134 pass 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
137def info_header(label: str) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
138 """Make a nice header string."""
139 return "--{:-<60s}".format(" " + label + " ") 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
142def info_formatter(info: Iterable[tuple[str, Any]]) -> Iterable[str]: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
143 """Produce a sequence of formatted lines from info.
145 `info` is a sequence of pairs (label, data). The produced lines are
146 nicely formatted, ready to print.
148 """
149 info = list(info) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
150 if not info: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
151 return 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
152 LABEL_LEN = 30 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
153 assert all(len(l) < LABEL_LEN for l, _ in info) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
154 for label, data in info: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
155 if data == []: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
156 data = "-none-" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
157 prefix = f"{label:>{LABEL_LEN}}: " 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
158 match data: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
159 case tuple() if len(str(data)) < 30: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
160 yield f"{prefix}{data}" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
161 case tuple() | list() | set(): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
162 for e in data: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
163 yield f"{prefix}{e}" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
164 prefix = " " * (LABEL_LEN + 2) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
165 case _: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
166 yield f"{prefix}{data}" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
169def write_formatted_info( 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
170 write: Callable[[str], None],
171 header: str,
172 info: Iterable[tuple[str, Any]],
173) -> None:
174 """Write a sequence of (label,data) pairs nicely.
176 `write` is a function write(str) that accepts each line of output.
177 `header` is a string to start the section. `info` is a sequence of
178 (label, data) pairs, where label is a str, and data can be a single
179 value, or a list/set/tuple.
181 """
182 write(info_header(header)) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
183 for line in info_formatter(info): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
184 write(f" {line}") 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
187def exc_one_line(exc: Exception) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
188 """Get a one-line summary of an exception, including class name and message."""
189 lines = traceback.format_exception_only(type(exc), exc) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
190 return "|".join(l.rstrip() for l in lines) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
193_FILENAME_REGEXES: list[tuple[str, str]] = [ 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
194 (r".*[/\\]pytest-of-.*[/\\]pytest-\d+([/\\]popen-gw\d+)?", "tmp:"),
195]
196_FILENAME_SUBS: list[tuple[str, str]] = [] 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
199@overload 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
200def short_filename(filename: str) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
201 pass
204@overload 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
205def short_filename(filename: None) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
206 pass
209def short_filename(filename: str | None) -> str | None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
210 """Shorten a file name. Directories are replaced by prefixes like 'syspath:'"""
211 if not _FILENAME_SUBS: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
212 for pathdir in sys.path: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
213 _FILENAME_SUBS.append((pathdir, "syspath:")) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
214 import coverage 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
216 _FILENAME_SUBS.append((os.path.dirname(coverage.__file__), "cov:")) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
217 _FILENAME_SUBS.sort(key=(lambda pair: len(pair[0])), reverse=True) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
218 if filename is not None: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
219 for pat, sub in _FILENAME_REGEXES: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
220 filename = re.sub(pat, sub, filename) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
221 for before, after in _FILENAME_SUBS: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
222 filename = filename.replace(before, after) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
223 return filename 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
226def file_summary(filename: str) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
227 """A one-line summary of a file, for log messages."""
228 try: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
229 s = os.stat(filename) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
230 except FileNotFoundError: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
231 summary = "does not exist" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
232 except Exception as e:
233 summary = f"error: {e}"
234 else:
235 mod = datetime.datetime.fromtimestamp(s.st_mtime) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
236 summary = f"{s.st_size} bytes, modified {mod}" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
237 return summary 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
240def short_stack( 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234
241 skip: int = 0,
242 full: bool = False,
243 frame_ids: bool = False,
244 short_filenames: bool = False,
245) -> str:
246 """Return a string summarizing the call stack.
248 The string is multi-line, with one line per stack frame. Each line shows
249 the function name, the file name, and the line number:
251 ...
252 start_import_stop : /Users/ned/coverage/trunk/tests/coveragetest.py:95
253 import_local_file : /Users/ned/coverage/trunk/tests/coveragetest.py:81
254 import_local_file : /Users/ned/coverage/trunk/coverage/backward.py:159
255 ...
257 `skip` is the number of closest immediate frames to skip, so that debugging
258 functions can call this and not be included in the result.
260 If `full` is true, then include all frames. Otherwise, initial "boring"
261 frames (ones in site-packages and earlier) are omitted.
263 `short_filenames` will shorten filenames using `short_filename`, to reduce
264 the amount of repetitive noise in stack traces.
266 """
267 # Regexes in initial frames that we don't care about.
268 # fmt: off
269 BORING_PRELUDE = [ 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
270 "<string>", # pytest-xdist has string execution.
271 r"\bigor.py$", # Our test runner.
272 r"\bsite-packages\b", # pytest etc getting to our tests.
273 ]
274 # fmt: on
276 stack: Iterable[inspect.FrameInfo] = inspect.stack()[:skip:-1] 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
277 if not full: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
278 for pat in BORING_PRELUDE: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
279 stack = itertools.dropwhile( 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
280 (lambda fi, pat=pat: re.search(pat, fi.filename)), # type: ignore[misc]
281 stack,
282 )
283 lines = [] 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
284 for frame_info in stack: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
285 line = f"{frame_info.function:>30s} : " 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
286 if frame_ids: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
287 line += f"{id(frame_info.frame):#x} " 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
288 filename = frame_info.filename 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
289 if short_filenames: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
290 filename = short_filename(filename) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
291 line += f"{filename}:{frame_info.lineno}" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
292 lines.append(line) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
293 return "\n".join(lines) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
296def dump_stack_frames(out: TWritable, skip: int = 0) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
297 """Print a summary of the stack to `out`."""
298 out.write(short_stack(skip=skip + 1) + "\n") 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
301def clipped_repr(text: str, numchars: int = 50) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
302 """`repr(text)`, but limited to `numchars`."""
303 r = reprlib.Repr() 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
304 r.maxstring = numchars 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
305 return r.repr(text) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
308def short_id(id64: int) -> int: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
309 """Given a 64-bit id, make a shorter 16-bit one."""
310 id16 = 0 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
311 for offset in range(0, 64, 16): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
312 id16 ^= id64 >> offset 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
313 return id16 & 0xFFFF 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
316def add_pid_and_tid(text: str) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
317 """A filter to add pid and tid to debug messages."""
318 # Thread ids are useful, but too long. Make a shorter one.
319 tid = f"{short_id(_thread.get_ident()):04x}" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
320 text = f"{os.getpid():5d}.{tid}: {text}" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
321 return text 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
324AUTO_REPR_IGNORE = {"$coverage.object_id"} 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
327def auto_repr(self: Any) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
328 """A function implementing an automatic __repr__ for debugging."""
329 show_attrs = ( 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
330 (k, v)
331 for k, v in self.__dict__.items()
332 if getattr(v, "show_repr_attr", True)
333 and not inspect.ismethod(v)
334 and k not in AUTO_REPR_IGNORE
335 )
336 return "<{klass} @{id:#x}{attrs}>".format( 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
337 klass=self.__class__.__name__,
338 id=id(self),
339 attrs="".join(f" {k}={v!r}" for k, v in show_attrs),
340 )
343def simplify(v: Any) -> Any: # pragma: debugging 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
344 """Turn things which are nearly dict/list/etc into dict/list/etc."""
345 if isinstance(v, dict):
346 return {k: simplify(vv) for k, vv in v.items()}
347 elif isinstance(v, (list, tuple)):
348 return type(v)(simplify(vv) for vv in v)
349 elif hasattr(v, "__dict__"):
350 return simplify({"." + k: v for k, v in v.__dict__.items()})
351 else:
352 return v
355def ppformat(v: Any) -> str: # pragma: debugging 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
356 """Debug helper to pretty-print data, including SimpleNamespace objects."""
357 return pprint.pformat(simplify(v), indent=4, compact=True, sort_dicts=True, width=140)
360def pp(v: Any) -> None: # pragma: debugging 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
361 """Debug helper to pretty-print data, including SimpleNamespace objects."""
362 print(ppformat(v))
365def filter_text(text: str, filters: Iterable[Callable[[str], str]]) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
366 """Run `text` through a series of filters.
368 `filters` is a list of functions. Each takes a string and returns a
369 string. Each is run in turn. After each filter, the text is split into
370 lines, and each line is passed through the next filter.
372 Returns: the final string that results after all of the filters have
373 run.
375 """
376 clean_text = text.rstrip() 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
377 ending = text[len(clean_text) :] 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
378 text = clean_text 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
379 for filter_fn in filters: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
380 lines = [] 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
381 for line in text.splitlines(): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
382 lines.extend(filter_fn(line).splitlines()) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
383 text = "\n".join(lines) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
384 return text + ending 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
387class CwdTracker: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
388 """A class to add cwd info to debug messages."""
390 def __init__(self) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
391 self.cwd: str | None = None 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
393 def filter(self, text: str) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
394 """Add a cwd message for each new cwd."""
395 cwd = os.getcwd() 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
396 if cwd != self.cwd: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
397 text = f"cwd is now {cwd!r}\n{text}" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
398 self.cwd = cwd 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
399 return text 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
402class ProcessTracker: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
403 """Track process creation for debug logging."""
405 def __init__(self) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
406 self.pid: int = os.getpid() 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
407 self.did_welcome = False 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
409 def filter(self, text: str) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
410 """Add a message about how new processes came to be."""
411 welcome = "" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
412 pid = os.getpid() 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
413 if self.pid != pid: 413 ↛ 414line 413 didn't jump to line 414 because the condition on line 413 was never true1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
414 welcome = f"New process: forked {self.pid} -> {pid}\n"
415 self.pid = pid
416 elif not self.did_welcome: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
417 argv = getattr(sys, "argv", None) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
418 welcome = ( 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
419 f"New process: {pid=}, executable: {sys.executable!r}\n"
420 + f"New process: cmd: {argv!r}\n"
421 + f"New process parent pid: {os.getppid()!r}\n"
422 )
424 if welcome: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
425 self.did_welcome = True 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
426 return welcome + text 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
427 else:
428 return text 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
431class PytestTracker: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
432 """Track the current pytest test name to add to debug messages."""
434 def __init__(self) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
435 self.test_name: str | None = None 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
437 def filter(self, text: str) -> str: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
438 """Add a message when the pytest test changes."""
439 test_name = os.getenv("PYTEST_CURRENT_TEST") 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
440 if test_name != self.test_name: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
441 text = f"Pytest context: {test_name}\n{text}" 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
442 self.test_name = test_name 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
443 return text 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
446class DebugOutputFile: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
447 """A file-like object that includes pid and cwd information."""
449 def __init__( 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
450 self,
451 outfile: IO[str] | None,
452 filters: Iterable[Callable[[str], str]],
453 ):
454 self.outfile = outfile 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
455 self.filters = list(filters) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
456 self.pid = os.getpid() 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
458 @classmethod 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
459 def get_one( 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234
460 cls,
461 fileobj: IO[str] | None = None,
462 file_name: str | None = None,
463 filters: Iterable[Callable[[str], str]] = (),
464 interim: bool = False,
465 ) -> DebugOutputFile:
466 """Get a DebugOutputFile.
468 If `fileobj` is provided, then a new DebugOutputFile is made with it.
470 If `fileobj` isn't provided, then a file is chosen (`file_name` if
471 provided, or COVERAGE_DEBUG_FILE, or stderr), and a process-wide
472 singleton DebugOutputFile is made.
474 `filters` are the text filters to apply to the stream to annotate with
475 pids, etc.
477 If `interim` is true, then a future `get_one` can replace this one.
479 """
480 if fileobj is not None: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
481 # Make DebugOutputFile around the fileobj passed.
482 return cls(fileobj, filters) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
484 the_one, is_interim = cls._get_singleton_data() 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
485 if the_one is None or is_interim: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
486 if file_name is not None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
487 fileobj = open(file_name, "a", encoding="utf-8") 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
488 else:
489 # $set_env.py: COVERAGE_DEBUG_FILE - Where to write debug output
490 file_name = os.getenv("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
491 if file_name in ["stdout", "stderr"]: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
492 fileobj = getattr(sys, file_name) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
493 elif file_name: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
494 fileobj = open(file_name, "a", encoding="utf-8") 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
495 atexit.register(fileobj.close) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
496 else:
497 fileobj = sys.stderr 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
498 the_one = cls(fileobj, filters) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
499 cls._set_singleton_data(the_one, interim) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
501 if not (the_one.filters): 501 ↛ 503line 501 didn't jump to line 503 because the condition on line 501 was always true1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
502 the_one.filters = list(filters) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
503 return the_one 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
505 # Because of the way igor.py deletes and re-imports modules,
506 # this class can be defined more than once. But we really want
507 # a process-wide singleton. So stash it in sys.modules instead of
508 # on a class attribute. Yes, this is aggressively gross.
510 SYS_MOD_NAME: Final[str] = "$coverage.debug.DebugOutputFile.the_one" 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
511 SINGLETON_ATTR: Final[str] = "the_one_and_is_interim" 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
513 @classmethod 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
514 def _set_singleton_data(cls, the_one: DebugOutputFile, interim: bool) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
515 """Set the one DebugOutputFile to rule them all."""
516 singleton_module = types.ModuleType(cls.SYS_MOD_NAME) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
517 setattr(singleton_module, cls.SINGLETON_ATTR, (the_one, interim)) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
518 sys.modules[cls.SYS_MOD_NAME] = singleton_module 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
520 @classmethod 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
521 def _get_singleton_data(cls) -> tuple[DebugOutputFile | None, bool]: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
522 """Get the one DebugOutputFile."""
523 singleton_module = sys.modules.get(cls.SYS_MOD_NAME) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
524 return getattr(singleton_module, cls.SINGLETON_ATTR, (None, True)) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
526 @classmethod 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
527 def _del_singleton_data(cls) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
528 """Delete the one DebugOutputFile, just for tests to use."""
529 if cls.SYS_MOD_NAME in sys.modules: 529 ↛ exitline 529 didn't return from function '_del_singleton_data' because the condition on line 529 was always true1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
530 del sys.modules[cls.SYS_MOD_NAME] 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
532 def write(self, text: str) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
533 """Just like file.write, but filter through all our filters."""
534 assert self.outfile is not None 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
535 if not self.outfile.closed: 535 ↛ exitline 535 didn't return from function 'write' because the condition on line 535 was always true1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
536 self.outfile.write(filter_text(text, self.filters)) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
537 self.outfile.flush() 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
539 def flush(self) -> None: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
540 """Flush our file."""
541 assert self.outfile is not None 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
542 if not self.outfile.closed: 542 ↛ exitline 542 didn't return from function 'flush' because the condition on line 542 was always true1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
543 self.outfile.flush() 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
546def log(msg: str, stack: bool = False) -> None: # pragma: debugging 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
547 """Write a log message as forcefully as possible."""
548 out = DebugOutputFile.get_one(interim=True)
549 out.write(msg + "\n")
550 if stack:
551 dump_stack_frames(out=out, skip=1)
554def decorate_methods( 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234
555 decorator: Callable[..., Any], 1abcdefghijklmnopqrEFG
556 butnot: Iterable[str] = (), 1stuvwxyzABCDabcdefghijklmnopqrEFG
557 private: bool = False, 1stuvwxyzABCDabcdefghijklmnopqrEFG
558) -> Callable[..., Any]: # pragma: debugging 1abcdefghijklmnopqrEFG
559 """A class decorator to apply a decorator to methods."""
561 def _decorator(cls): # type: ignore[no-untyped-def]
562 for name, meth in inspect.getmembers(cls, inspect.isroutine):
563 if name not in cls.__dict__:
564 continue
565 if name != "__init__":
566 if not private and name.startswith("_"):
567 continue
568 if name in butnot:
569 continue
570 setattr(cls, name, decorator(meth))
571 return cls
573 return _decorator
576def break_in_pudb(func: AnyCallable) -> AnyCallable: # pragma: debugging 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
577 """A function decorator to stop in the debugger for each call."""
579 @functools.wraps(func)
580 def _wrapper(*args: Any, **kwargs: Any) -> Any:
581 import pudb
583 sys.stdout = sys.__stdout__
584 pudb.set_trace()
585 return func(*args, **kwargs)
587 return _wrapper
590OBJ_IDS = itertools.count() 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
591CALLS = itertools.count() 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
592OBJ_ID_ATTR = "$coverage.object_id" 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
595def show_calls( 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234
596 show_args: bool = True, 1stuvwxyzABCDabcdefghijklmnopqrEFG
597 show_stack: bool = False, 1stuvwxyzABCDabcdefghijklmnopqrEFG
598 show_return: bool = False, 1stuvwxyzABCDabcdefghijklmnopqrEFG
599) -> Callable[..., Any]: # pragma: debugging 1abcdefghijklmnopqrEFG
600 """A method decorator to debug-log each call to the function."""
602 def _decorator(func: AnyCallable) -> AnyCallable:
603 @functools.wraps(func)
604 def _wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
605 oid = getattr(self, OBJ_ID_ATTR, None)
606 if oid is None:
607 oid = f"{os.getpid():08d} {next(OBJ_IDS):04d}"
608 setattr(self, OBJ_ID_ATTR, oid)
609 extra = ""
610 if show_args:
611 eargs = ", ".join(map(repr, args))
612 ekwargs = ", ".join("{}={!r}".format(*item) for item in kwargs.items())
613 extra += "("
614 extra += eargs
615 if eargs and ekwargs:
616 extra += ", "
617 extra += ekwargs
618 extra += ")"
619 if show_stack:
620 extra += " @ "
621 extra += "; ".join(short_stack(short_filenames=True).splitlines())
622 callid = next(CALLS)
623 msg = f"{oid} {callid:04d} {func.__name__}{extra}\n"
624 DebugOutputFile.get_one(interim=True).write(msg)
625 ret = func(self, *args, **kwargs)
626 if show_return:
627 msg = f"{oid} {callid:04d} {func.__name__} return {ret!r}\n"
628 DebugOutputFile.get_one(interim=True).write(msg)
629 return ret
631 return _wrapper
633 return _decorator
636def relevant_environment_display(env: Mapping[str, str]) -> list[tuple[str, str]]: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
637 """Filter environment variables for a debug display.
639 Select variables to display (with COV or PY in the name, or HOME, TEMP, or
640 TMP), and also cloak sensitive values with asterisks.
642 Arguments:
643 env: a dict of environment variable names and values.
645 Returns:
646 A list of pairs (name, value) to show.
648 """
649 SLUGS = {"COV", "PY"} 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
650 INCLUDE = {"HOME", "TEMP", "TMP"} 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
651 CLOAK = {"API", "TOKEN", "KEY", "SECRET", "PASS", "SIGNATURE"} 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
652 TRUNCATE = {"COVERAGE_PROCESS_CONFIG"} 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
653 TRUNCATE_LEN = 60 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
655 to_show = [] 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
656 for name, val in env.items(): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
657 show = False 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
658 if name in INCLUDE: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
659 show = True 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
660 elif any(slug in name for slug in SLUGS): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
661 show = True 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
662 if show: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
663 if any(slug in name for slug in CLOAK): 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
664 val = re.sub(r"\w", "*", val) 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
665 if name in TRUNCATE: 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
666 if len(val) > TRUNCATE_LEN: 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
667 val = val[: TRUNCATE_LEN - 3] + "..." 1stuvwxyzABCDabcdefghijklmnopqrHIJKLMNOPQRSTUVWXYZ01234EFG
668 to_show.append((name, val)) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG
669 return human_sorted_items(to_show) 1stuvwxyzABCDabcdefghijklmnopqrHI6JK7LM%NO8PQ9RS'TU!VW#XY(Z0$12534)EFG