1616import traceback
1717
1818from dataclasses import dataclass
19- from types import CodeType , FrameType
19+ from types import CodeType
2020from typing import (
2121 Any ,
2222 Callable ,
23- TYPE_CHECKING ,
23+ NewType ,
24+ Optional ,
2425 cast ,
2526)
2627
4445
4546# pylint: disable=unused-argument
4647
47- LOG = False
48+ # $set_env.py: COVERAGE_LOG_SYSMON - Log sys.monitoring activity
49+ LOG = bool (int (os .getenv ("COVERAGE_LOG_SYSMON" , 0 )))
4850
4951# This module will be imported in all versions of Python, but only used in 3.12+
5052# It will be type-checked for 3.12, but not for earlier versions.
5153sys_monitoring = getattr (sys , "monitoring" , None )
5254
53- if TYPE_CHECKING :
54- assert sys_monitoring is not None
55- # I want to say this but it's not allowed:
56- # MonitorReturn = Literal[sys.monitoring.DISABLE] | None
57- MonitorReturn = Any
55+ DISABLE_TYPE = NewType ("DISABLE_TYPE" , object )
56+ MonitorReturn = Optional [DISABLE_TYPE ]
57+ DISABLE = cast (MonitorReturn , getattr (sys_monitoring , "DISABLE" , None ))
5858
5959
6060if LOG : # pragma: debugging
@@ -77,7 +77,10 @@ def _wrapped(*args: Any, **kwargs: Any) -> Any:
7777 assert sys_monitoring is not None
7878
7979 short_stack = functools .partial (
80- short_stack , full = True , short_filenames = True , frame_ids = True ,
80+ short_stack ,
81+ full = True ,
82+ short_filenames = True ,
83+ frame_ids = True ,
8184 )
8285 seen_threads : set [int ] = set ()
8386
@@ -131,7 +134,9 @@ def _wrapped(self: Any, *args: Any) -> Any:
131134 return ret
132135 except Exception as exc :
133136 log (f"!!{ exc .__class__ .__name__ } : { exc } " )
134- log ("" .join (traceback .format_exception (exc ))) # pylint: disable=[no-value-for-parameter]
137+ if 1 :
138+ # pylint: disable=no-value-for-parameter
139+ log ("" .join (traceback .format_exception (exc )))
135140 try :
136141 assert sys_monitoring is not None
137142 sys_monitoring .set_events (sys .monitoring .COVERAGE_ID , 0 )
@@ -205,7 +210,6 @@ def __init__(self, tool_id: int) -> None:
205210 # A list of code_objects, just to keep them alive so that id's are
206211 # useful as identity.
207212 self .code_objects : list [CodeType ] = []
208- self .last_lines : dict [FrameType , int ] = {}
209213 # Map id(code_object) -> code_object
210214 self .local_event_codes : dict [int , CodeType ] = {}
211215 self .sysmon_on = False
@@ -231,20 +235,20 @@ def start(self) -> None:
231235 assert sys_monitoring is not None
232236 sys_monitoring .use_tool_id (self .myid , "coverage.py" )
233237 register = functools .partial (sys_monitoring .register_callback , self .myid )
234- events = sys_monitoring .events
238+ events = sys .monitoring .events
239+ import contextlib
240+
241+ with open ("/tmp/foo.out" , "a" ) as f :
242+ with contextlib .redirect_stdout (f ):
243+ print (f"{ events = } " )
244+ sys_monitoring .set_events (self .myid , events .PY_START )
245+ register (events .PY_START , self .sysmon_py_start )
235246 if self .trace_arcs :
236- sys_monitoring .set_events (
237- self .myid ,
238- events .PY_START | events .PY_UNWIND ,
239- )
240- register (events .PY_START , self .sysmon_py_start )
241- register (events .PY_RESUME , self .sysmon_py_resume_arcs )
242- register (events .PY_RETURN , self .sysmon_py_return_arcs )
243- register (events .PY_UNWIND , self .sysmon_py_unwind_arcs )
247+ register (events .PY_RETURN , self .sysmon_py_return )
244248 register (events .LINE , self .sysmon_line_arcs )
249+ register (events .BRANCH_TAKEN , self .sysmon_branch_taken )
250+ register (events .BRANCH_NOT_TAKEN , self .sysmon_branch_not_taken )
245251 else :
246- sys_monitoring .set_events (self .myid , events .PY_START )
247- register (events .PY_START , self .sysmon_py_start )
248252 register (events .LINE , self .sysmon_line_lines )
249253 sys_monitoring .restart_events ()
250254 self .sysmon_on = True
@@ -282,23 +286,10 @@ def get_stats(self) -> dict[str, int] | None:
282286 """Return a dictionary of statistics, or None."""
283287 return None
284288
285- # The number of frames in callers_frame takes @panopticon into account.
286- if LOG :
287-
288- def callers_frame (self ) -> FrameType :
289- """Get the frame of the Python code we're monitoring."""
290- return (
291- inspect .currentframe ().f_back .f_back .f_back # type: ignore[union-attr,return-value]
292- )
293-
294- else :
295-
296- def callers_frame (self ) -> FrameType :
297- """Get the frame of the Python code we're monitoring."""
298- return inspect .currentframe ().f_back .f_back # type: ignore[union-attr,return-value]
299-
300289 @panopticon ("code" , "@" )
301- def sysmon_py_start (self , code : CodeType , instruction_offset : int ) -> MonitorReturn :
290+ def sysmon_py_start ( # pylint: disable=useless-return
291+ self , code : CodeType , instruction_offset : int
292+ ) -> MonitorReturn :
302293 """Handle sys.monitoring.events.PY_START events."""
303294 # Entering a new frame. Decide if we should trace in this file.
304295 self ._activity = True
@@ -350,91 +341,78 @@ def sysmon_py_start(self, code: CodeType, instruction_offset: int) -> MonitorRet
350341 with self .lock :
351342 if self .sysmon_on :
352343 assert sys_monitoring is not None
353- sys_monitoring .set_local_events (
354- self .myid ,
355- code ,
356- events .PY_RETURN
357- #
358- | events .PY_RESUME
359- # | events.PY_YIELD
360- | events .LINE
361- | events .BRANCH_TAKEN
362- | events .BRANCH_NOT_TAKEN
363- # | events.JUMP
364- )
344+ local_events = events .PY_RETURN | events .PY_RESUME | events .LINE
345+ if self .trace_arcs :
346+ assert env .PYBEHAVIOR .branch_taken
347+ local_events |= (
348+ events .BRANCH_TAKEN | events .BRANCH_NOT_TAKEN
349+ )
350+ sys_monitoring .set_local_events (self .myid , code , local_events )
365351 self .local_event_codes [id (code )] = code
366352
367- if tracing_code and self .trace_arcs :
368- frame = self .callers_frame ()
369- self .last_lines [frame ] = - code .co_firstlineno
370- return None
371- else :
372- return sys .monitoring .DISABLE
373-
374- @panopticon ("code" , "@" )
375- def sysmon_py_resume_arcs (
376- self , code : CodeType , instruction_offset : int ,
377- ) -> MonitorReturn :
378- """Handle sys.monitoring.events.PY_RESUME events for branch coverage."""
379- frame = self .callers_frame ()
380- self .last_lines [frame ] = frame .f_lineno
353+ return None
381354
382355 @panopticon ("code" , "@" , None )
383- def sysmon_py_return_arcs (
384- self , code : CodeType , instruction_offset : int , retval : object ,
356+ def sysmon_py_return ( # pylint: disable=useless-return
357+ self ,
358+ code : CodeType ,
359+ instruction_offset : int ,
360+ retval : object ,
385361 ) -> MonitorReturn :
386362 """Handle sys.monitoring.events.PY_RETURN events for branch coverage."""
387- frame = self .callers_frame ()
388363 code_info = self .code_infos .get (id (code ))
389364 if code_info is not None and code_info .file_data is not None :
390- last_line = self .last_lines .get (frame )
365+ assert code_info .byte_to_line is not None
366+ last_line = code_info .byte_to_line [instruction_offset ]
391367 if last_line is not None :
392368 arc = (last_line , - code .co_firstlineno )
393- # log(f"adding {arc=}")
394369 cast (set [TArc ], code_info .file_data ).add (arc )
395-
396- # Leaving this function, no need for the frame any more.
397- self .last_lines .pop (frame , None )
398-
399- @panopticon ("code" , "@" , "exc" )
400- def sysmon_py_unwind_arcs (
401- self , code : CodeType , instruction_offset : int , exception : BaseException ,
402- ) -> MonitorReturn :
403- """Handle sys.monitoring.events.PY_UNWIND events for branch coverage."""
404- frame = self .callers_frame ()
405- # Leaving this function.
406- last_line = self .last_lines .pop (frame , None )
407- if isinstance (exception , GeneratorExit ):
408- # We don't want to count generator exits as arcs.
409- return
410- code_info = self .code_infos .get (id (code ))
411- if code_info is not None and code_info .file_data is not None :
412- if last_line is not None :
413- arc = (last_line , - code .co_firstlineno )
414- # log(f"adding {arc=}")
415- cast (set [TArc ], code_info .file_data ).add (arc )
416-
370+ log (f"adding { arc = } " )
371+ return None
417372
418373 @panopticon ("code" , "line" )
419374 def sysmon_line_lines (self , code : CodeType , line_number : int ) -> MonitorReturn :
420375 """Handle sys.monitoring.events.LINE events for line coverage."""
421376 code_info = self .code_infos [id (code )]
422377 if code_info .file_data is not None :
423378 cast (set [TLineNo ], code_info .file_data ).add (line_number )
424- # log(f"adding {line_number=}")
425- return sys . monitoring . DISABLE
379+ log (f"adding { line_number = } " )
380+ return DISABLE
426381
427382 @panopticon ("code" , "line" )
428383 def sysmon_line_arcs (self , code : CodeType , line_number : int ) -> MonitorReturn :
429384 """Handle sys.monitoring.events.LINE events for branch coverage."""
430385 code_info = self .code_infos [id (code )]
431- ret = None
432386 if code_info .file_data is not None :
433- frame = self .callers_frame ()
434- last_line = self .last_lines .get (frame )
435- if last_line is not None :
436- arc = (last_line , line_number )
437- cast (set [TArc ], code_info .file_data ).add (arc )
438- # log(f"adding {arc=}")
439- self .last_lines [frame ] = line_number
440- return ret
387+ arc = (line_number , line_number )
388+ cast (set [TArc ], code_info .file_data ).add (arc )
389+ log (f"adding { arc = } " )
390+ return DISABLE
391+
392+ @panopticon ("code" , "@" , "@" )
393+ def sysmon_branch_taken (
394+ self , code : CodeType , instruction_offset : int , destination_offset : int
395+ ) -> MonitorReturn :
396+ """Handed BRANCH_TAKEN and BRANCH_NOT_TAKEN events."""
397+ code_info = self .code_infos [id (code )]
398+ if code_info .file_data is not None :
399+ b2l = code_info .byte_to_line
400+ assert b2l is not None
401+ arc = (b2l [instruction_offset ], b2l [destination_offset ])
402+ cast (set [TArc ], code_info .file_data ).add (arc )
403+ log (f"adding { arc = } " )
404+ return DISABLE
405+
406+ @panopticon ("code" , "@" , "@" )
407+ def sysmon_branch_not_taken (
408+ self , code : CodeType , instruction_offset : int , destination_offset : int
409+ ) -> MonitorReturn :
410+ """Handed BRANCH_TAKEN and BRANCH_NOT_TAKEN events."""
411+ code_info = self .code_infos [id (code )]
412+ if code_info .file_data is not None :
413+ b2l = code_info .byte_to_line
414+ assert b2l is not None
415+ arc = (b2l [instruction_offset ], b2l [destination_offset ])
416+ cast (set [TArc ], code_info .file_data ).add (arc )
417+ log (f"adding { arc = } " )
418+ return DISABLE
0 commit comments