3
3
4
4
"""Control of and utilities for debugging."""
5
5
6
+ from __future__ import annotations
7
+
6
8
import contextlib
7
9
import functools
8
10
import inspect
15
17
import types
16
18
import _thread
17
19
18
- from typing import Any , Callable , Iterable , Iterator , Tuple
20
+ from typing import (
21
+ Any , Callable , Generator , IO , Iterable , Iterator , Optional , List , Tuple ,
22
+ cast ,
23
+ )
19
24
20
25
from coverage .misc import isolate_module
21
26
25
30
# When debugging, it can be helpful to force some options, especially when
26
31
# debugging the configuration mechanisms you usually use to control debugging!
27
32
# This is a list of forced debugging options.
28
- FORCED_DEBUG = []
33
+ FORCED_DEBUG : List [ str ] = []
29
34
FORCED_DEBUG_FILE = None
30
35
31
36
@@ -34,7 +39,7 @@ class DebugControl:
34
39
35
40
show_repr_attr = False # For AutoReprMixin
36
41
37
- def __init__ (self , options , output ) :
42
+ def __init__ (self , options : Iterable [ str ] , output : Optional [ IO [ str ]]) -> None :
38
43
"""Configure the options and output file for debugging."""
39
44
self .options = list (options ) + FORCED_DEBUG
40
45
self .suppress_callers = False
@@ -49,17 +54,17 @@ def __init__(self, options, output):
49
54
)
50
55
self .raw_output = self .output .outfile
51
56
52
- def __repr__ (self ):
57
+ def __repr__ (self ) -> str :
53
58
return f"<DebugControl options={ self .options !r} raw_output={ self .raw_output !r} >"
54
59
55
- def should (self , option ) :
60
+ def should (self , option : str ) -> bool :
56
61
"""Decide whether to output debug information in category `option`."""
57
62
if option == "callers" and self .suppress_callers :
58
63
return False
59
64
return (option in self .options )
60
65
61
66
@contextlib .contextmanager
62
- def without_callers (self ):
67
+ def without_callers (self ) -> Generator [ None , None , None ] :
63
68
"""A context manager to prevent call stacks from being logged."""
64
69
old = self .suppress_callers
65
70
self .suppress_callers = True
@@ -68,7 +73,7 @@ def without_callers(self):
68
73
finally :
69
74
self .suppress_callers = old
70
75
71
- def write (self , msg ) :
76
+ def write (self , msg : str ) -> None :
72
77
"""Write a line of debug output.
73
78
74
79
`msg` is the line to write. A newline will be appended.
@@ -86,26 +91,26 @@ def write(self, msg):
86
91
87
92
class DebugControlString (DebugControl ):
88
93
"""A `DebugControl` that writes to a StringIO, for testing."""
89
- def __init__ (self , options ) :
94
+ def __init__ (self , options : Iterable [ str ]) -> None :
90
95
super ().__init__ (options , io .StringIO ())
91
96
92
- def get_output (self ):
97
+ def get_output (self ) -> str :
93
98
"""Get the output text from the `DebugControl`."""
94
- return self .raw_output .getvalue ()
99
+ return cast ( str , self .raw_output .getvalue () )
95
100
96
101
97
102
class NoDebugging :
98
103
"""A replacement for DebugControl that will never try to do anything."""
99
- def should (self , option ): # pylint: disable=unused-argument
104
+ def should (self , option : str ) -> bool : # pylint: disable=unused-argument
100
105
"""Should we write debug messages? Never."""
101
106
return False
102
107
103
- def write (self , msg ) :
108
+ def write (self , msg : str ) -> None :
104
109
"""This will never be called."""
105
110
raise AssertionError ("NoDebugging.write should never be called." )
106
111
107
112
108
- def info_header (label ) :
113
+ def info_header (label : str ) -> str :
109
114
"""Make a nice header string."""
110
115
return "--{:-<60s}" .format (" " + label + " " )
111
116
@@ -155,7 +160,7 @@ def write_formatted_info(
155
160
write (f" { line } " )
156
161
157
162
158
- def short_stack (limit = None , skip = 0 ):
163
+ def short_stack (limit : Optional [ int ] = None , skip : int = 0 ) -> str :
159
164
"""Return a string summarizing the call stack.
160
165
161
166
The string is multi-line, with one line per stack frame. Each line shows
@@ -177,29 +182,33 @@ def short_stack(limit=None, skip=0):
177
182
return "\n " .join ("%30s : %s:%d" % (t [3 ], t [1 ], t [2 ]) for t in stack )
178
183
179
184
180
- def dump_stack_frames (limit = None , out = None , skip = 0 ):
185
+ def dump_stack_frames (
186
+ limit : Optional [int ]= None ,
187
+ out : Optional [IO [str ]]= None ,
188
+ skip : int = 0
189
+ ) -> None :
181
190
"""Print a summary of the stack to stdout, or someplace else."""
182
191
out = out or sys .stdout
183
192
out .write (short_stack (limit = limit , skip = skip + 1 ))
184
193
out .write ("\n " )
185
194
186
195
187
- def clipped_repr (text , numchars = 50 ):
196
+ def clipped_repr (text : str , numchars : int = 50 ) -> str :
188
197
"""`repr(text)`, but limited to `numchars`."""
189
198
r = reprlib .Repr ()
190
199
r .maxstring = numchars
191
200
return r .repr (text )
192
201
193
202
194
- def short_id (id64 ) :
203
+ def short_id (id64 : int ) -> int :
195
204
"""Given a 64-bit id, make a shorter 16-bit one."""
196
205
id16 = 0
197
206
for offset in range (0 , 64 , 16 ):
198
207
id16 ^= id64 >> offset
199
208
return id16 & 0xFFFF
200
209
201
210
202
- def add_pid_and_tid (text ) :
211
+ def add_pid_and_tid (text : str ) -> str :
203
212
"""A filter to add pid and tid to debug messages."""
204
213
# Thread ids are useful, but too long. Make a shorter one.
205
214
tid = f"{ short_id (_thread .get_ident ()):04x} "
@@ -211,7 +220,7 @@ class AutoReprMixin:
211
220
"""A mixin implementing an automatic __repr__ for debugging."""
212
221
auto_repr_ignore = ['auto_repr_ignore' , '$coverage.object_id' ]
213
222
214
- def __repr__ (self ):
223
+ def __repr__ (self ) -> str :
215
224
show_attrs = (
216
225
(k , v ) for k , v in self .__dict__ .items ()
217
226
if getattr (v , "show_repr_attr" , True )
@@ -225,7 +234,7 @@ def __repr__(self):
225
234
)
226
235
227
236
228
- def simplify (v ): # pragma: debugging
237
+ def simplify (v : Any ) -> Any : # pragma: debugging
229
238
"""Turn things which are nearly dict/list/etc into dict/list/etc."""
230
239
if isinstance (v , dict ):
231
240
return {k :simplify (vv ) for k , vv in v .items ()}
@@ -237,13 +246,13 @@ def simplify(v): # pragma: debugging
237
246
return v
238
247
239
248
240
- def pp (v ): # pragma: debugging
249
+ def pp (v : Any ) -> None : # pragma: debugging
241
250
"""Debug helper to pretty-print data, including SimpleNamespace objects."""
242
251
# Might not be needed in 3.9+
243
252
pprint .pprint (simplify (v ))
244
253
245
254
246
- def filter_text (text , filters ) :
255
+ def filter_text (text : str , filters : Iterable [ Callable [[ str ], str ]]) -> str :
247
256
"""Run `text` through a series of filters.
248
257
249
258
`filters` is a list of functions. Each takes a string and returns a
@@ -266,10 +275,10 @@ def filter_text(text, filters):
266
275
267
276
class CwdTracker : # pragma: debugging
268
277
"""A class to add cwd info to debug messages."""
269
- def __init__ (self ):
270
- self .cwd = None
278
+ def __init__ (self ) -> None :
279
+ self .cwd : Optional [ str ] = None
271
280
272
- def filter (self , text ) :
281
+ def filter (self , text : str ) -> str :
273
282
"""Add a cwd message for each new cwd."""
274
283
cwd = os .getcwd ()
275
284
if cwd != self .cwd :
@@ -280,7 +289,12 @@ def filter(self, text):
280
289
281
290
class DebugOutputFile : # pragma: debugging
282
291
"""A file-like object that includes pid and cwd information."""
283
- def __init__ (self , outfile , show_process , filters ):
292
+ def __init__ (
293
+ self ,
294
+ outfile : Optional [IO [str ]],
295
+ show_process : bool ,
296
+ filters : Iterable [Callable [[str ], str ]],
297
+ ):
284
298
self .outfile = outfile
285
299
self .show_process = show_process
286
300
self .filters = list (filters )
@@ -296,7 +310,13 @@ def __init__(self, outfile, show_process, filters):
296
310
SINGLETON_ATTR = 'the_one_and_is_interim'
297
311
298
312
@classmethod
299
- def get_one (cls , fileobj = None , show_process = True , filters = (), interim = False ):
313
+ def get_one (
314
+ cls ,
315
+ fileobj : Optional [IO [str ]]= None ,
316
+ show_process : bool = True ,
317
+ filters : Iterable [Callable [[str ], str ]]= (),
318
+ interim : bool = False ,
319
+ ) -> DebugOutputFile :
300
320
"""Get a DebugOutputFile.
301
321
302
322
If `fileobj` is provided, then a new DebugOutputFile is made with it.
@@ -339,13 +359,15 @@ def get_one(cls, fileobj=None, show_process=True, filters=(), interim=False):
339
359
sys .modules [cls .SYS_MOD_NAME ] = singleton_module
340
360
return the_one
341
361
342
- def write (self , text ) :
362
+ def write (self , text : str ) -> None :
343
363
"""Just like file.write, but filter through all our filters."""
364
+ assert self .outfile is not None
344
365
self .outfile .write (filter_text (text , self .filters ))
345
366
self .outfile .flush ()
346
367
347
- def flush (self ):
368
+ def flush (self ) -> None :
348
369
"""Flush our file."""
370
+ assert self .outfile is not None
349
371
self .outfile .flush ()
350
372
351
373
@@ -388,7 +410,11 @@ def _wrapper(*args, **kwargs):
388
410
CALLS = itertools .count ()
389
411
OBJ_ID_ATTR = "$coverage.object_id"
390
412
391
- def show_calls (show_args = True , show_stack = False , show_return = False ): # pragma: debugging
413
+ def show_calls (
414
+ show_args : bool = True ,
415
+ show_stack : bool = False ,
416
+ show_return : bool = False ,
417
+ ) -> Callable [..., Any ]: # pragma: debugging
392
418
"""A method decorator to debug-log each call to the function."""
393
419
def _decorator (func ):
394
420
@functools .wraps (func )
@@ -422,7 +448,7 @@ def _wrapper(self, *args, **kwargs):
422
448
return _decorator
423
449
424
450
425
- def _clean_stack_line (s ): # pragma: debugging
451
+ def _clean_stack_line (s : str ) -> str : # pragma: debugging
426
452
"""Simplify some paths in a stack trace, for compactness."""
427
453
s = s .strip ()
428
454
s = s .replace (os .path .dirname (__file__ ) + '/' , '' )
0 commit comments