diff --git a/coverage/core.py b/coverage/core.py index 7affeb2e2..3a0717f48 100644 --- a/coverage/core.py +++ b/coverage/core.py @@ -61,6 +61,10 @@ def __init__(self, warn: TWarnFn, config: CoverageConfig, metacov: bool) -> None warn("sys.monitoring isn't available, using default core", slug="no-sysmon") core_name = None + if core_name == "sysmon" and config.branch and not env.PYBEHAVIOR.branch_taken: + warn("sys.monitoring can't yet measure branches well, using default core", slug="no-sysmon") + core_name = None + if not core_name: # Once we're comfortable with sysmon as a default: # if env.PYBEHAVIOR.pep669 and self.should_start_context is None: diff --git a/coverage/env.py b/coverage/env.py index 792ed1d5f..43a99ac8b 100644 --- a/coverage/env.py +++ b/coverage/env.py @@ -115,7 +115,7 @@ class PYBEHAVIOR: # Does sys.monitoring support BRANCH_TAKEN? branch_taken = ( pep669 and - hasattr(sys.monitoring.events, "BRANCH_TAKEN") # type:ignore[attr-defined] + hasattr(sys.monitoring.events, "BRANCH_TAKEN") # type:ignore[attr-defined,unused-ignore] ) diff --git a/coverage/sysmon.py b/coverage/sysmon.py index bbbe42390..827d92188 100644 --- a/coverage/sysmon.py +++ b/coverage/sysmon.py @@ -49,7 +49,7 @@ if TYPE_CHECKING: assert sys_monitoring is not None # I want to say this but it's not allowed: - # MonitorReturn = Literal[sys.monitoring.DISABLE] | None + # MonitorReturn = Literal[sys.monitoring.DISABLE] | None MonitorReturn = Any @@ -73,7 +73,10 @@ def _wrapped(*args: Any, **kwargs: Any) -> Any: assert sys_monitoring is not None short_stack = functools.partial( - short_stack, full=True, short_filenames=True, frame_ids=True, + short_stack, + full=True, + short_filenames=True, + frame_ids=True, ) seen_threads: set[int] = set() @@ -127,7 +130,9 @@ def _wrapped(self: Any, *args: Any) -> Any: return ret except Exception as exc: log(f"!!{exc.__class__.__name__}: {exc}") - log("".join(traceback.format_exception(exc))) # pylint: disable=[no-value-for-parameter] + if 1: + # pylint: disable=no-value-for-parameter + log("".join(traceback.format_exception(exc))) try: assert sys_monitoring is not None sys_monitoring.set_events(sys.monitoring.COVERAGE_ID, 0) @@ -201,7 +206,6 @@ def __init__(self, tool_id: int) -> None: # A list of code_objects, just to keep them alive so that id's are # useful as identity. self.code_objects: list[CodeType] = [] - self.last_lines: dict[FrameType, int] = {} # Map id(code_object) -> code_object self.local_event_codes: dict[int, CodeType] = {} self.sysmon_on = False @@ -346,30 +350,27 @@ def sysmon_py_start(self, code: CodeType, instruction_offset: int) -> MonitorRet with self.lock: if self.sysmon_on: assert sys_monitoring is not None - sys_monitoring.set_local_events( - self.myid, - code, + local_events = ( events.PY_RETURN # | events.PY_RESUME # | events.PY_YIELD | events.LINE - | events.BRANCH_TAKEN - | events.BRANCH_NOT_TAKEN # | events.JUMP ) + if self.trace_arcs: + assert env.PYBEHAVIOR.branch_taken + local_events |= events.BRANCH_TAKEN | events.BRANCH_NOT_TAKEN + sys_monitoring.set_local_events(self.myid, code, local_events) self.local_event_codes[id(code)] = code - if tracing_code and self.trace_arcs: - frame = self.callers_frame() - self.last_lines[frame] = -code.co_firstlineno - return None - else: - return sys.monitoring.DISABLE + return sys.monitoring.DISABLE @panopticon("code", "@") def sysmon_py_resume_arcs( - self, code: CodeType, instruction_offset: int, + self, + code: CodeType, + instruction_offset: int, ) -> MonitorReturn: """Handle sys.monitoring.events.PY_RESUME events for branch coverage.""" frame = self.callers_frame() @@ -377,7 +378,10 @@ def sysmon_py_resume_arcs( @panopticon("code", "@", None) def sysmon_py_return_arcs( - self, code: CodeType, instruction_offset: int, retval: object, + self, + code: CodeType, + instruction_offset: int, + retval: object, ) -> MonitorReturn: """Handle sys.monitoring.events.PY_RETURN events for branch coverage.""" frame = self.callers_frame() @@ -394,7 +398,10 @@ def sysmon_py_return_arcs( @panopticon("code", "@", "exc") def sysmon_py_unwind_arcs( - self, code: CodeType, instruction_offset: int, exception: BaseException, + self, + code: CodeType, + instruction_offset: int, + exception: BaseException, ) -> MonitorReturn: """Handle sys.monitoring.events.PY_UNWIND events for branch coverage.""" frame = self.callers_frame() @@ -410,7 +417,6 @@ def sysmon_py_unwind_arcs( # log(f"adding {arc=}") cast(Set[TArc], code_info.file_data).add(arc) - @panopticon("code", "line") def sysmon_line_lines(self, code: CodeType, line_number: int) -> MonitorReturn: """Handle sys.monitoring.events.LINE events for line coverage.""" diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 94728641a..ad1e3d392 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -554,13 +554,19 @@ def test_multiprocessing_with_branching(self, start_method: str) -> None: code = (SQUARE_OR_CUBE_WORK + MULTI_CODE).format(NPROCS=nprocs, UPTO=upto) total = sum(x*x if x%2 else x*x*x for x in range(upto)) expected_out = f"{nprocs} pids, total = {total}" + expect_warn = ( + env.PYBEHAVIOR.pep669 + and (not env.PYBEHAVIOR.branch_taken) + and testenv.SYS_MON + ) self.make_file("multi.py", code) self.make_file("multi.rc", """\ [run] concurrency = multiprocessing branch = True omit = */site-packages/* - """) + """ + ("disable_warnings = no-sysmon" if expect_warn else "") + ) out = self.run_command(f"coverage run --rcfile=multi.rc multi.py {start_method}") assert out.rstrip() == expected_out