Skip to content

Commit b8c236a

Browse files
committed
fix: multi-line with-statements exit correctly. #1880
1 parent 64b7a45 commit b8c236a

File tree

4 files changed

+54
-5
lines changed

4 files changed

+54
-5
lines changed

CHANGES.rst

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ upgrading your version of coverage.py.
2323
Unreleased
2424
----------
2525

26-
Nothing yet.
26+
- fix: multi-line ``with`` statements could cause contained branches to be
27+
incorrectly marked as missing (`issue 1880`_). This is now fixed.
28+
29+
.. _issue 1880: https://github.com/nedbat/coveragepy/issues/1880
2730

2831

2932
.. start-releases

coverage/env.py

+4
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ class PYBEHAVIOR:
8181
# When leaving a with-block, do we visit the with-line again for the exit?
8282
exit_through_with = (PYVERSION >= (3, 10, 0, "beta"))
8383

84+
# When leaving a with-block, do we visit the with-line exactly,
85+
# or the inner-most context manager?
86+
exit_with_through_ctxmgr = (PYVERSION >= (3, 12))
87+
8488
# Match-case construct.
8589
match_case = (PYVERSION >= (3, 10))
8690

coverage/parser.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -1261,12 +1261,19 @@ def _handle__While(self, node: ast.While) -> set[ArcStart]:
12611261
return exits
12621262

12631263
def _handle__With(self, node: ast.With) -> set[ArcStart]:
1264-
start = self.line_for_node(node)
1264+
if env.PYBEHAVIOR.exit_with_through_ctxmgr:
1265+
starts = [self.line_for_node(item.context_expr) for item in node.items]
1266+
else:
1267+
starts = [self.line_for_node(node)]
12651268
if env.PYBEHAVIOR.exit_through_with:
1266-
self.current_with_starts.add(start)
1267-
self.all_with_starts.add(start)
1268-
exits = self.process_body(node.body, from_start=ArcStart(start))
1269+
for start in starts:
1270+
self.current_with_starts.add(start)
1271+
self.all_with_starts.add(start)
1272+
1273+
exits = self.process_body(node.body, from_start=ArcStart(starts[-1]))
1274+
12691275
if env.PYBEHAVIOR.exit_through_with:
1276+
start = starts[-1]
12701277
self.current_with_starts.remove(start)
12711278
with_exit = {ArcStart(start)}
12721279
if exits:

tests/test_arcs.py

+35
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,41 @@ def test_with_with_lambda(self) -> None:
347347
branchz_missing="",
348348
)
349349

350+
def test_multiline_with(self) -> None:
351+
# https://github.com/nedbat/coveragepy/issues/1880
352+
self.check_coverage("""\
353+
import contextlib, itertools
354+
nums = itertools.count()
355+
with (
356+
contextlib.nullcontext() as x,
357+
):
358+
while next(nums) < 6:
359+
y = 7
360+
z = 8
361+
""",
362+
branchz="67 68",
363+
branchz_missing="",
364+
)
365+
366+
367+
def test_multi_multiline_with(self) -> None:
368+
# https://github.com/nedbat/coveragepy/issues/1880
369+
self.check_coverage("""\
370+
import contextlib, itertools
371+
nums = itertools.count()
372+
with (
373+
contextlib.nullcontext() as x,
374+
contextlib.nullcontext() as y,
375+
contextlib.nullcontext() as z,
376+
):
377+
while next(nums) < 8:
378+
y = 9
379+
z = 10
380+
""",
381+
branchz="89 8A",
382+
branchz_missing="",
383+
)
384+
350385

351386
class LoopArcTest(CoverageTest):
352387
"""Arc-measuring tests involving loops."""

0 commit comments

Comments
 (0)