From 58586e51fbd8fdcb6794c166cdb53d4d02684e87 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 12 Jan 2024 09:15:44 -0500 Subject: [PATCH] fix: sysmon should ignore GeneratorExit Also, don't use dis in production, it's changed often. --- coverage/sysmon.py | 23 ++++++++++++----------- tests/test_arcs.py | 3 --- tests/test_coverage.py | 2 -- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/coverage/sysmon.py b/coverage/sysmon.py index 793c0c248..ab231fde1 100644 --- a/coverage/sysmon.py +++ b/coverage/sysmon.py @@ -6,7 +6,6 @@ from __future__ import annotations import dataclasses -import dis import functools import inspect import os @@ -168,12 +167,10 @@ class CodeInfo: def bytes_to_lines(code: CodeType) -> Dict[int, int]: """Make a dict mapping byte code offsets to line numbers.""" b2l = {} - cur_line = 0 - for inst in dis.get_instructions(code): - if inst.starts_line is not None: - cur_line = inst.starts_line - b2l[inst.offset] = cur_line - log(f" --> bytes_to_lines: {b2l!r}") + for bstart, bend, lineno in code.co_lines(): + if lineno is not None: + for boffset in range(bstart, bend, 2): + b2l[boffset] = lineno return b2l @@ -379,26 +376,30 @@ def sysmon_py_return_arcs( last_line = self.last_lines.get(frame) if last_line is not None: arc = (last_line, -code.co_firstlineno) + # log(f"adding {arc=}") cast(Set[TArc], code_info.file_data).add(arc) # Leaving this function, no need for the frame any more. self.last_lines.pop(frame, None) - @panopticon("code", "@", None) + @panopticon("code", "@", "exc") def sysmon_py_unwind_arcs( self, code: CodeType, instruction_offset: int, exception: BaseException ) -> MonitorReturn: """Handle sys.monitoring.events.PY_UNWIND events for branch coverage.""" frame = self.callers_frame() + # Leaving this function. + last_line = self.last_lines.pop(frame, None) + if isinstance(exception, GeneratorExit): + # We don't want to count generator exits as arcs. + return code_info = self.code_infos.get(id(code)) if code_info is not None and code_info.file_data is not None: - last_line = self.last_lines.get(frame) if last_line is not None: arc = (last_line, -code.co_firstlineno) + # log(f"adding {arc=}") cast(Set[TArc], code_info.file_data).add(arc) - # Leaving this function. - self.last_lines.pop(frame, None) @panopticon("code", "line") def sysmon_line_lines(self, code: CodeType, line_number: int) -> MonitorReturn: diff --git a/tests/test_arcs.py b/tests/test_arcs.py index 55132b896..7331eb32a 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -7,7 +7,6 @@ import pytest -from tests import testenv from tests.coveragetest import CoverageTest from tests.helpers import assert_count_equal, xfail_pypy38 @@ -1308,7 +1307,6 @@ def gen(inp): arcz=".1 19 9. .2 23 34 45 56 63 37 7.", ) - @pytest.mark.xfail(testenv.SYS_MON, reason="TODO: fix this for sys.monitoring") def test_abandoned_yield(self) -> None: # https://github.com/nedbat/coveragepy/issues/440 self.check_coverage("""\ @@ -1651,7 +1649,6 @@ def test_pathologically_long_code_object(self, n: int) -> None: self.check_coverage(code, arcs=[(-1, 1), (1, 2*n+4), (2*n+4, -1)]) assert self.stdout() == f"{n}\n" - @pytest.mark.xfail(testenv.SYS_MON, reason="TODO: fix this for sys.monitoring") def test_partial_generators(self) -> None: # https://github.com/nedbat/coveragepy/issues/475 # Line 2 is executed completely. diff --git a/tests/test_coverage.py b/tests/test_coverage.py index e4157ecf0..96639c072 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -11,7 +11,6 @@ from coverage import env from coverage.exceptions import NoDataError -from tests import testenv from tests.coveragetest import CoverageTest @@ -1407,7 +1406,6 @@ def test_excluding_try_except_stranded_else(self) -> None: arcz_missing=arcz_missing, ) - @pytest.mark.xfail(testenv.SYS_MON, reason="TODO: fix this for sys.monitoring") def test_excluded_comprehension_branches(self) -> None: # https://github.com/nedbat/coveragepy/issues/1271 self.check_coverage("""\