26 # This is a list of forced debugging options. |
24 # This is a list of forced debugging options. |
27 FORCED_DEBUG = [] |
25 FORCED_DEBUG = [] |
28 FORCED_DEBUG_FILE = None |
26 FORCED_DEBUG_FILE = None |
29 |
27 |
30 |
28 |
31 class DebugControl(object): |
29 class DebugControl: |
32 """Control and output for debugging.""" |
30 """Control and output for debugging.""" |
33 |
31 |
34 show_repr_attr = False # For SimpleReprMixin |
32 show_repr_attr = False # For SimpleReprMixin |
35 |
33 |
36 def __init__(self, options, output): |
34 def __init__(self, options, output): |
47 filters=filters, |
45 filters=filters, |
48 ) |
46 ) |
49 self.raw_output = self.output.outfile |
47 self.raw_output = self.output.outfile |
50 |
48 |
51 def __repr__(self): |
49 def __repr__(self): |
52 return "<DebugControl options=%r raw_output=%r>" % (self.options, self.raw_output) |
50 return f"<DebugControl options={self.options!r} raw_output={self.raw_output!r}>" |
53 |
51 |
54 def should(self, option): |
52 def should(self, option): |
55 """Decide whether to output debug information in category `option`.""" |
53 """Decide whether to output debug information in category `option`.""" |
56 if option == "callers" and self.suppress_callers: |
54 if option == "callers" and self.suppress_callers: |
57 return False |
55 return False |
75 """ |
73 """ |
76 self.output.write(msg+"\n") |
74 self.output.write(msg+"\n") |
77 if self.should('self'): |
75 if self.should('self'): |
78 caller_self = inspect.stack()[1][0].f_locals.get('self') |
76 caller_self = inspect.stack()[1][0].f_locals.get('self') |
79 if caller_self is not None: |
77 if caller_self is not None: |
80 self.output.write("self: {!r}\n".format(caller_self)) |
78 self.output.write(f"self: {caller_self!r}\n") |
81 if self.should('callers'): |
79 if self.should('callers'): |
82 dump_stack_frames(out=self.output, skip=1) |
80 dump_stack_frames(out=self.output, skip=1) |
83 self.output.flush() |
81 self.output.flush() |
84 |
82 |
85 |
83 |
86 class DebugControlString(DebugControl): |
84 class DebugControlString(DebugControl): |
87 """A `DebugControl` that writes to a StringIO, for testing.""" |
85 """A `DebugControl` that writes to a StringIO, for testing.""" |
88 def __init__(self, options): |
86 def __init__(self, options): |
89 super(DebugControlString, self).__init__(options, StringIO()) |
87 super().__init__(options, io.StringIO()) |
90 |
88 |
91 def get_output(self): |
89 def get_output(self): |
92 """Get the output text from the `DebugControl`.""" |
90 """Get the output text from the `DebugControl`.""" |
93 return self.raw_output.getvalue() |
91 return self.raw_output.getvalue() |
94 |
92 |
95 |
93 |
96 class NoDebugging(object): |
94 class NoDebugging: |
97 """A replacement for DebugControl that will never try to do anything.""" |
95 """A replacement for DebugControl that will never try to do anything.""" |
98 def should(self, option): # pylint: disable=unused-argument |
96 def should(self, option): # pylint: disable=unused-argument |
99 """Should we write debug messages? Never.""" |
97 """Should we write debug messages? Never.""" |
100 return False |
98 return False |
101 |
99 |
181 |
179 |
182 |
180 |
183 def add_pid_and_tid(text): |
181 def add_pid_and_tid(text): |
184 """A filter to add pid and tid to debug messages.""" |
182 """A filter to add pid and tid to debug messages.""" |
185 # Thread ids are useful, but too long. Make a shorter one. |
183 # Thread ids are useful, but too long. Make a shorter one. |
186 tid = "{:04x}".format(short_id(_thread.get_ident())) |
184 tid = f"{short_id(_thread.get_ident()):04x}" |
187 text = "{:5d}.{}: {}".format(os.getpid(), tid, text) |
185 text = f"{os.getpid():5d}.{tid}: {text}" |
188 return text |
186 return text |
189 |
187 |
190 |
188 |
191 class SimpleReprMixin(object): |
189 class SimpleReprMixin: |
192 """A mixin implementing a simple __repr__.""" |
190 """A mixin implementing a simple __repr__.""" |
193 simple_repr_ignore = ['simple_repr_ignore', '$coverage.object_id'] |
191 simple_repr_ignore = ['simple_repr_ignore', '$coverage.object_id'] |
194 |
192 |
195 def __repr__(self): |
193 def __repr__(self): |
196 show_attrs = ( |
194 show_attrs = ( |
200 and k not in self.simple_repr_ignore |
198 and k not in self.simple_repr_ignore |
201 ) |
199 ) |
202 return "<{klass} @0x{id:x} {attrs}>".format( |
200 return "<{klass} @0x{id:x} {attrs}>".format( |
203 klass=self.__class__.__name__, |
201 klass=self.__class__.__name__, |
204 id=id(self), |
202 id=id(self), |
205 attrs=" ".join("{}={!r}".format(k, v) for k, v in show_attrs), |
203 attrs=" ".join(f"{k}={v!r}" for k, v in show_attrs), |
206 ) |
204 ) |
207 |
205 |
208 |
206 |
209 def simplify(v): # pragma: debugging |
207 def simplify(v): # pragma: debugging |
210 """Turn things which are nearly dict/list/etc into dict/list/etc.""" |
208 """Turn things which are nearly dict/list/etc into dict/list/etc.""" |
243 lines.extend(fn(line).splitlines()) |
241 lines.extend(fn(line).splitlines()) |
244 text = "\n".join(lines) |
242 text = "\n".join(lines) |
245 return text + ending |
243 return text + ending |
246 |
244 |
247 |
245 |
248 class CwdTracker(object): # pragma: debugging |
246 class CwdTracker: # pragma: debugging |
249 """A class to add cwd info to debug messages.""" |
247 """A class to add cwd info to debug messages.""" |
250 def __init__(self): |
248 def __init__(self): |
251 self.cwd = None |
249 self.cwd = None |
252 |
250 |
253 def filter(self, text): |
251 def filter(self, text): |
254 """Add a cwd message for each new cwd.""" |
252 """Add a cwd message for each new cwd.""" |
255 cwd = os.getcwd() |
253 cwd = os.getcwd() |
256 if cwd != self.cwd: |
254 if cwd != self.cwd: |
257 text = "cwd is now {!r}\n".format(cwd) + text |
255 text = f"cwd is now {cwd!r}\n" + text |
258 self.cwd = cwd |
256 self.cwd = cwd |
259 return text |
257 return text |
260 |
258 |
261 |
259 |
262 class DebugOutputFile(object): # pragma: debugging |
260 class DebugOutputFile: # pragma: debugging |
263 """A file-like object that includes pid and cwd information.""" |
261 """A file-like object that includes pid and cwd information.""" |
264 def __init__(self, outfile, show_process, filters): |
262 def __init__(self, outfile, show_process, filters): |
265 self.outfile = outfile |
263 self.outfile = outfile |
266 self.show_process = show_process |
264 self.show_process = show_process |
267 self.filters = list(filters) |
265 self.filters = list(filters) |
268 |
266 |
269 if self.show_process: |
267 if self.show_process: |
270 self.filters.insert(0, CwdTracker().filter) |
268 self.filters.insert(0, CwdTracker().filter) |
271 self.write("New process: executable: %r\n" % (sys.executable,)) |
269 self.write(f"New process: executable: {sys.executable!r}\n") |
272 self.write("New process: cmd: %r\n" % (getattr(sys, 'argv', None),)) |
270 self.write("New process: cmd: {!r}\n".format(getattr(sys, 'argv', None))) |
273 if hasattr(os, 'getppid'): |
271 if hasattr(os, 'getppid'): |
274 self.write("New process: pid: %r, parent pid: %r\n" % (os.getpid(), os.getppid())) |
272 self.write(f"New process: pid: {os.getpid()!r}, parent pid: {os.getppid()!r}\n") |
275 |
273 |
276 SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one' |
274 SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one' |
277 |
275 |
278 @classmethod |
276 @classmethod |
279 def get_one(cls, fileobj=None, show_process=True, filters=(), interim=False): |
277 def get_one(cls, fileobj=None, show_process=True, filters=(), interim=False): |
304 # on a class attribute. Yes, this is aggressively gross. |
302 # on a class attribute. Yes, this is aggressively gross. |
305 the_one, is_interim = sys.modules.get(cls.SYS_MOD_NAME, (None, True)) |
303 the_one, is_interim = sys.modules.get(cls.SYS_MOD_NAME, (None, True)) |
306 if the_one is None or is_interim: |
304 if the_one is None or is_interim: |
307 if fileobj is None: |
305 if fileobj is None: |
308 debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE) |
306 debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE) |
309 if debug_file_name: |
307 if debug_file_name in ("stdout", "stderr"): |
|
308 fileobj = getattr(sys, debug_file_name) |
|
309 elif debug_file_name: |
310 fileobj = open(debug_file_name, "a") |
310 fileobj = open(debug_file_name, "a") |
311 else: |
311 else: |
312 fileobj = sys.stderr |
312 fileobj = sys.stderr |
313 the_one = cls(fileobj, show_process, filters) |
313 the_one = cls(fileobj, show_process, filters) |
314 sys.modules[cls.SYS_MOD_NAME] = (the_one, interim) |
314 sys.modules[cls.SYS_MOD_NAME] = (the_one, interim) |
368 def _decorator(func): |
368 def _decorator(func): |
369 @functools.wraps(func) |
369 @functools.wraps(func) |
370 def _wrapper(self, *args, **kwargs): |
370 def _wrapper(self, *args, **kwargs): |
371 oid = getattr(self, OBJ_ID_ATTR, None) |
371 oid = getattr(self, OBJ_ID_ATTR, None) |
372 if oid is None: |
372 if oid is None: |
373 oid = "{:08d} {:04d}".format(os.getpid(), next(OBJ_IDS)) |
373 oid = f"{os.getpid():08d} {next(OBJ_IDS):04d}" |
374 setattr(self, OBJ_ID_ATTR, oid) |
374 setattr(self, OBJ_ID_ATTR, oid) |
375 extra = "" |
375 extra = "" |
376 if show_args: |
376 if show_args: |
377 eargs = ", ".join(map(repr, args)) |
377 eargs = ", ".join(map(repr, args)) |
378 ekwargs = ", ".join("{}={!r}".format(*item) for item in kwargs.items()) |
378 ekwargs = ", ".join("{}={!r}".format(*item) for item in kwargs.items()) |
384 extra += ")" |
384 extra += ")" |
385 if show_stack: |
385 if show_stack: |
386 extra += " @ " |
386 extra += " @ " |
387 extra += "; ".join(_clean_stack_line(l) for l in short_stack().splitlines()) |
387 extra += "; ".join(_clean_stack_line(l) for l in short_stack().splitlines()) |
388 callid = next(CALLS) |
388 callid = next(CALLS) |
389 msg = "{} {:04d} {}{}\n".format(oid, callid, func.__name__, extra) |
389 msg = f"{oid} {callid:04d} {func.__name__}{extra}\n" |
390 DebugOutputFile.get_one(interim=True).write(msg) |
390 DebugOutputFile.get_one(interim=True).write(msg) |
391 ret = func(self, *args, **kwargs) |
391 ret = func(self, *args, **kwargs) |
392 if show_return: |
392 if show_return: |
393 msg = "{} {:04d} {} return {!r}\n".format(oid, callid, func.__name__, ret) |
393 msg = f"{oid} {callid:04d} {func.__name__} return {ret!r}\n" |
394 DebugOutputFile.get_one(interim=True).write(msg) |
394 DebugOutputFile.get_one(interim=True).write(msg) |
395 return ret |
395 return ret |
396 return _wrapper |
396 return _wrapper |
397 return _decorator |
397 return _decorator |
398 |
398 |