Skip to content

Commit

Permalink
fix: multi-line with-statements exit correctly. #1880
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Oct 20, 2024
1 parent 64b7a45 commit b8c236a
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 5 deletions.
5 changes: 4 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ upgrading your version of coverage.py.
Unreleased
----------

Nothing yet.
- fix: multi-line ``with`` statements could cause contained branches to be
incorrectly marked as missing (`issue 1880`_). This is now fixed.

.. _issue 1880: https://github.com/nedbat/coveragepy/issues/1880


.. start-releases
Expand Down
4 changes: 4 additions & 0 deletions coverage/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ class PYBEHAVIOR:
# When leaving a with-block, do we visit the with-line again for the exit?
exit_through_with = (PYVERSION >= (3, 10, 0, "beta"))

# When leaving a with-block, do we visit the with-line exactly,
# or the inner-most context manager?
exit_with_through_ctxmgr = (PYVERSION >= (3, 12))

# Match-case construct.
match_case = (PYVERSION >= (3, 10))

Expand Down
15 changes: 11 additions & 4 deletions coverage/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1261,12 +1261,19 @@ def _handle__While(self, node: ast.While) -> set[ArcStart]:
return exits

def _handle__With(self, node: ast.With) -> set[ArcStart]:
start = self.line_for_node(node)
if env.PYBEHAVIOR.exit_with_through_ctxmgr:
starts = [self.line_for_node(item.context_expr) for item in node.items]
else:
starts = [self.line_for_node(node)]
if env.PYBEHAVIOR.exit_through_with:
self.current_with_starts.add(start)
self.all_with_starts.add(start)
exits = self.process_body(node.body, from_start=ArcStart(start))
for start in starts:
self.current_with_starts.add(start)
self.all_with_starts.add(start)

exits = self.process_body(node.body, from_start=ArcStart(starts[-1]))

if env.PYBEHAVIOR.exit_through_with:
start = starts[-1]
self.current_with_starts.remove(start)
with_exit = {ArcStart(start)}
if exits:
Expand Down
35 changes: 35 additions & 0 deletions tests/test_arcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,41 @@ def test_with_with_lambda(self) -> None:
branchz_missing="",
)

def test_multiline_with(self) -> None:
# https://github.com/nedbat/coveragepy/issues/1880
self.check_coverage("""\
import contextlib, itertools
nums = itertools.count()
with (
contextlib.nullcontext() as x,
):
while next(nums) < 6:
y = 7
z = 8
""",
branchz="67 68",
branchz_missing="",
)


def test_multi_multiline_with(self) -> None:
# https://github.com/nedbat/coveragepy/issues/1880
self.check_coverage("""\
import contextlib, itertools
nums = itertools.count()
with (
contextlib.nullcontext() as x,
contextlib.nullcontext() as y,
contextlib.nullcontext() as z,
):
while next(nums) < 8:
y = 9
z = 10
""",
branchz="89 8A",
branchz_missing="",
)


class LoopArcTest(CoverageTest):
"""Arc-measuring tests involving loops."""
Expand Down

0 comments on commit b8c236a

Please sign in to comment.