15
15
import sys
16
16
import threading
17
17
import traceback
18
+ import weakref
18
19
19
20
from types import CodeType , FrameType , ModuleType
20
21
from typing import Any , Callable , Dict , List , Optional , Set , Tuple , cast
21
22
22
- #from coverage.debug import short_stack
23
23
from coverage .types import (
24
24
TArc , TFileDisposition , TLineNo , TTraceData , TTraceFileData , TTraceFn ,
25
25
TTracer , TWarnFn ,
26
26
)
27
27
28
- # When running meta-coverage, this file can try to trace itself, which confuses
29
- # everything. Don't trace ourselves.
30
28
31
- THIS_FILE = __file__ .rstrip ("co" )
29
+ class LoggingWrapper :
30
+ def __init__ (self , wrapped , namespace ):
31
+ self .wrapped = wrapped
32
+ self .namespace = namespace
33
+
34
+ def __getattr__ (self , name ):
35
+ def _wrapped (* args , ** kwargs ):
36
+ log (f"{ self .namespace } .{ name } { args } { kwargs } " )
37
+ return getattr (self .wrapped , name )(* args , ** kwargs )
38
+ return _wrapped
39
+
40
+ #sys_monitoring = LoggingWrapper(sys.monitoring, "sys.monitoring")
41
+ sys_monitoring = getattr (sys , "monitoring" , None )
32
42
33
43
seen_threads = set ()
34
44
@@ -37,7 +47,7 @@ def log(msg):
37
47
# Thread ids are reused across processes? Make a shorter number more likely
38
48
# to be unique.
39
49
pid = os .getpid ()
40
- tid = (os . getpid () * threading .current_thread ().ident ) % 9_999_991
50
+ tid = (pid * threading .current_thread ().ident ) % 9_999_991
41
51
tid = f"{ tid :07d} "
42
52
if tid not in seen_threads :
43
53
seen_threads .add (tid )
@@ -55,7 +65,7 @@ def log(msg):
55
65
]
56
66
FILENAME_SUBS = []
57
67
58
- def fname_repr (filename ):
68
+ def short_fname (filename ):
59
69
if not FILENAME_SUBS :
60
70
for pathdir in sys .path :
61
71
FILENAME_SUBS .append ((pathdir , "syspath:" ))
@@ -67,38 +77,41 @@ def fname_repr(filename):
67
77
filename = re .sub (pat , sub , filename )
68
78
for before , after in FILENAME_SUBS :
69
79
filename = filename .replace (before , after )
70
- return repr ( filename )
80
+ return filename
71
81
72
82
def arg_repr (arg ):
73
83
if isinstance (arg , CodeType ):
74
- arg_repr = f"<name={ arg .co_name } , file={ fname_repr (arg .co_filename )} #{ arg .co_firstlineno } >"
84
+ arg_repr = f"<code @ { id ( arg ):#x } name={ arg .co_name } , file={ short_fname (arg .co_filename )!r } #{ arg .co_firstlineno } >"
75
85
else :
76
86
arg_repr = repr (arg )
77
87
return arg_repr
78
88
79
89
def short_stack (full = True ):
80
90
stack : Iterable [inspect .FrameInfo ] = inspect .stack ()[::- 1 ]
81
- return "\n " .join (f"{ fi .function :>30s} : 0x { id (fi .frame ):x} { fi .filename } :{ fi .lineno } " for fi in stack )
91
+ return "\n " .join (f"{ fi .function :>30s} : { id (fi .frame ):# x} { short_fname ( fi .filename ) } :{ fi .lineno } " for fi in stack )
82
92
83
93
def panopticon (* names ):
84
94
def _decorator (meth ):
85
95
def _wrapped (self , * args ):
86
96
try :
87
97
# log("stack:\n" + short_stack())
88
- # args_reprs = []
89
- # for name, arg in zip(names, args):
90
- # if name is None:
91
- # continue
92
- # args_reprs.append(f"{name}={arg_repr(arg)}")
93
- # log(f"{id(self)}:{meth.__name__}({', '.join(args_reprs)})")
98
+ args_reprs = []
99
+ for name , arg in zip (names , args ):
100
+ if name is None :
101
+ continue
102
+ args_reprs .append (f"{ name } ={ arg_repr (arg )} " )
103
+ log (f"{ id (self ):#x } :{ meth .__name__ } ({ ', ' .join (args_reprs )} )" )
94
104
ret = meth (self , * args )
95
- # log(f" end {id(self)}:{meth.__name__}({', '.join(args_reprs)})")
105
+ #log(f" end {id(self):#x }:{meth.__name__}({', '.join(args_reprs)})")
96
106
return ret
97
107
except Exception as exc :
98
- log (f"{ exc .__class__ .__name__ } : { exc } " )
99
- with open ("/tmp/pan.out" , "a" ) as f :
100
- traceback .print_exception (exc , file = f )
101
- sys .monitoring .set_events (sys .monitoring .COVERAGE_ID , 0 )
108
+ log (f"!!{ exc .__class__ .__name__ } : { exc } " )
109
+ log ("" .join (traceback .format_exception (exc )))
110
+ try :
111
+ sys_monitoring .set_events (sys .monitoring .COVERAGE_ID , 0 )
112
+ except ValueError :
113
+ # We might have already shut off monitoring.
114
+ log (f"oops, shutting off events with disabled tool id" )
102
115
raise
103
116
return _wrapped
104
117
return _decorator
@@ -126,7 +139,8 @@ class Pep669Tracer(TTracer):
126
139
# One of these will be used across threads. Be careful.
127
140
128
141
def __init__ (self ) -> None :
129
- log (f"Pep669Tracer.__init__: @{ id (self )} \n { short_stack ()} " )
142
+ test_name = os .environ .get ("PYTEST_CURRENT_TEST" , "no-test" )
143
+ log (f"Pep669Tracer.__init__: @{ id (self ):#x} in { test_name } \n { short_stack ()} " )
130
144
# pylint: disable=super-init-not-called
131
145
# Attributes set from the collector:
132
146
self .data : TTraceData
@@ -137,16 +151,14 @@ def __init__(self) -> None:
137
151
self .switch_context : Optional [Callable [[Optional [str ]], None ]] = None
138
152
self .warn : TWarnFn
139
153
140
- # The threading module to use, if any.
141
- self .threading : Optional [ModuleType ] = None
142
-
143
154
self .code_infos : Dict [CodeType , CodeInfo ] = {}
144
155
self .last_lines : Dict [FrameType , int ] = {}
156
+ self .local_event_codes = None
157
+
145
158
self .stats = {
146
159
"starts" : 0 ,
147
160
}
148
161
149
- self .thread : Optional [threading .Thread ] = None
150
162
self .stopped = False
151
163
self ._activity = False
152
164
@@ -155,45 +167,40 @@ def __init__(self) -> None:
155
167
atexit .register (setattr , self , "in_atexit" , True )
156
168
157
169
def __repr__ (self ) -> str :
158
- me = id (self )
159
170
points = sum (len (v ) for v in self .data .values ())
160
171
files = len (self .data )
161
- return f"<Pep669Tracer at 0x { me : x} : { points } data points in { files } files>"
172
+ return f"<Pep669Tracer at { id ( self ):# x} : { points } data points in { files } files>"
162
173
174
+ @panopticon ()
163
175
def start (self ) -> TTraceFn : # TODO: wrong return type
164
176
"""Start this Tracer."""
165
177
self .stopped = False
166
- if self .threading :
167
- if self .thread is None :
168
- self .thread = self .threading .current_thread ()
169
- else :
170
- if self .thread .ident != self .threading .current_thread ().ident :
171
- # Re-starting from a different thread!? Don't set the trace
172
- # function, but we are marked as running again, so maybe it
173
- # will be ok?
174
- 1 / 0
175
- return self ._cached_bound_method_trace
176
178
179
+ self .local_event_codes = weakref .WeakSet ()
177
180
self .myid = sys .monitoring .COVERAGE_ID
178
- sys . monitoring .use_tool_id (self .myid , "coverage.py" )
181
+ sys_monitoring .use_tool_id (self .myid , "coverage.py" )
179
182
events = sys .monitoring .events
180
- sys . monitoring .set_events (
183
+ sys_monitoring .set_events (
181
184
self .myid ,
182
- events .PY_START | events .PY_RETURN | events . PY_RESUME | events . PY_YIELD | events . PY_UNWIND ,
185
+ events .PY_START | events .PY_UNWIND ,
183
186
)
184
- sys .monitoring .register_callback (self .myid , events .PY_START , self .sysmon_py_start )
185
- sys .monitoring .register_callback (self .myid , events .PY_RESUME , self .sysmon_py_resume )
186
- sys .monitoring .register_callback (self .myid , events .PY_RETURN , self .sysmon_py_return )
187
- sys .monitoring .register_callback (self .myid , events .PY_YIELD , self .sysmon_py_yield )
188
- sys .monitoring .register_callback (self .myid , events .PY_UNWIND , self .sysmon_py_unwind )
189
- sys .monitoring .register_callback (self .myid , events .LINE , self .sysmon_line )
190
- sys .monitoring .register_callback (self .myid , events .BRANCH , self .sysmon_branch )
191
- sys .monitoring .register_callback (self .myid , events .JUMP , self .sysmon_jump )
192
-
187
+ sys_monitoring .register_callback (self .myid , events .PY_START , self .sysmon_py_start )
188
+ sys_monitoring .register_callback (self .myid , events .PY_RESUME , self .sysmon_py_resume )
189
+ sys_monitoring .register_callback (self .myid , events .PY_RETURN , self .sysmon_py_return )
190
+ sys_monitoring .register_callback (self .myid , events .PY_YIELD , self .sysmon_py_yield )
191
+ sys_monitoring .register_callback (self .myid , events .PY_UNWIND , self .sysmon_py_unwind )
192
+ sys_monitoring .register_callback (self .myid , events .LINE , self .sysmon_line )
193
+ sys_monitoring .register_callback (self .myid , events .BRANCH , self .sysmon_branch )
194
+ sys_monitoring .register_callback (self .myid , events .JUMP , self .sysmon_jump )
195
+
196
+ @panopticon ()
193
197
def stop (self ) -> None :
194
198
"""Stop this Tracer."""
195
- sys .monitoring .set_events (self .myid , 0 )
196
- sys .monitoring .free_tool_id (self .myid )
199
+ sys_monitoring .set_events (self .myid , 0 )
200
+ for code in self .local_event_codes :
201
+ sys_monitoring .set_local_events (self .myid , code , 0 )
202
+ self .local_event_codes = None
203
+ sys_monitoring .free_tool_id (self .myid )
197
204
198
205
def activity (self ) -> bool :
199
206
"""Has there been any activity?"""
@@ -255,19 +262,20 @@ def sysmon_py_start(self, code, instruction_offset: int):
255
262
256
263
if tracing_code :
257
264
events = sys .monitoring .events
258
- log (f"set_local_events(code={ arg_repr (code )} )" )
259
- sys .monitoring .set_local_events (
265
+ sys_monitoring .set_local_events (
260
266
self .myid ,
261
267
code ,
262
- sys .monitoring .events .LINE |
263
- sys .monitoring .events .BRANCH |
264
- sys .monitoring .events .JUMP ,
268
+ events .PY_RETURN | events .PY_RESUME | events .PY_YIELD |
269
+ events .LINE |
270
+ events .BRANCH |
271
+ events .JUMP ,
265
272
)
273
+ self .local_event_codes .add (code )
266
274
267
275
if tracing_code :
268
276
frame = self .callers_frame ()
269
277
self .last_lines [frame ] = - code .co_firstlineno
270
- log (f" { file_data = } " )
278
+ # log(f" {file_data=}")
271
279
272
280
@panopticon ("code" , "@" )
273
281
def sysmon_py_resume (self , code , instruction_offset : int ):
@@ -282,10 +290,10 @@ def sysmon_py_return(self, code, instruction_offset: int, retval: object):
282
290
if self .trace_arcs :
283
291
arc = (self .last_lines [frame ], - code .co_firstlineno )
284
292
cast (Set [TArc ], code_info .file_data ).add (arc )
285
- log (f" add1({ arc = } )" )
293
+ # log(f" add1({arc=})")
286
294
287
295
# Leaving this function, no need for the frame any more.
288
- log (f" popping frame 0x { id (frame ):x} " )
296
+ # log(f" popping frame {id(frame):# x}")
289
297
self .last_lines .pop (frame , None )
290
298
291
299
@panopticon ("code" , "@" , None )
@@ -300,29 +308,38 @@ def sysmon_py_unwind(self, code, instruction_offset: int, exception):
300
308
if self .trace_arcs :
301
309
arc = (self .last_lines [frame ], - code .co_firstlineno )
302
310
cast (Set [TArc ], code_info .file_data ).add (arc )
303
- log (f" add3({ arc = } )" )
311
+ # log(f" add3({arc=})")
304
312
305
313
# Leaving this function.
306
314
self .last_lines .pop (frame , None )
307
315
308
316
@panopticon ("code" , "line" )
309
317
def sysmon_line (self , code , line_number : int ):
310
- frame = self .callers_frame ()
311
318
code_info = self .code_infos [code ]
319
+ if not code_info .tracing :
320
+ log ("DISABLE" )
321
+ return sys .monitoring .DISABLE
312
322
if code_info .file_data is not None :
323
+ frame = self .callers_frame ()
313
324
if self .trace_arcs :
314
325
arc = (self .last_lines [frame ], line_number )
315
326
cast (Set [TArc ], code_info .file_data ).add (arc )
316
- log (f" add4({ arc = } )" )
327
+ # log(f" add4({arc=})")
317
328
else :
318
329
cast (Set [TLineNo ], code_info .file_data ).add (line_number )
319
- log (f" add5({ line_number = } )" )
330
+ # log(f" add5({line_number=})")
320
331
self .last_lines [frame ] = line_number
321
332
322
333
@panopticon ("code" , "from@" , "to@" )
323
334
def sysmon_branch (self , code , instruction_offset : int , destination_offset : int ):
324
- ...
335
+ code_info = self .code_infos [code ]
336
+ if not code_info .tracing :
337
+ log ("DISABLE" )
338
+ return sys .monitoring .DISABLE
325
339
326
340
@panopticon ("code" , "from@" , "to@" )
327
341
def sysmon_jump (self , code , instruction_offset : int , destination_offset : int ):
328
- ...
342
+ code_info = self .code_infos [code ]
343
+ if not code_info .tracing :
344
+ log ("DISABLE" )
345
+ return sys .monitoring .DISABLE
0 commit comments