fix: nested context managers shouldn't cause a phantom missing branch… · nedbat/coveragepy@378c321 (original) (raw)

3 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -23,10 +23,15 @@ upgrading your version of coverage.py.
23 23 Unreleased
24 24 ----------
25 25
26 +- Fix: nested context managers could incorrectly be analyzed to flag a missing
27 + branch on the last context manager, as described in `issue 1876`_. This is
28 + now fixed.
29 +
26 30 - Fix: the missing branch message about not exiting a module had an extra
27 31 "didn't," as described in `issue 1873`_. This is now fixed.
28 32
29 33 .. _issue 1873: https://github.com/nedbat/coveragepy/issues/1873
34 +.. _issue 1876: https://github.com/nedbat/coveragepy/issues/1876
30 35
31 36
32 37 .. start-releases
Original file line number Diff line number Diff line change
@@ -315,12 +315,18 @@ def fix_with_jumps(self, arcs: Iterable[TArc]) -> set[TArc]:
315 315 to_add = set()
316 316 for arc in arcs:
317 317 if arc in self._with_jump_fixers:
318 +start = arc[0]
319 +to_remove.add(arc)
318 320 start_next, prev_next = self._with_jump_fixers[arc]
319 -if start_next in arcs:
320 -to_add.add(prev_next)
321 -to_remove.add(arc)
321 +while start_next in self._with_jump_fixers:
322 322 to_remove.add(start_next)
323 -return (set(arcs) | to_add) - to_remove
323 +start_next, prev_next = self._with_jump_fixers[start_next]
324 +to_remove.add(prev_next)
325 +to_add.add((start, prev_next[1]))
326 +to_remove.add(arc)
327 +to_remove.add(start_next)
328 +arcs = (set(arcs) | to_add) - to_remove
329 +return arcs
324 330
325 331 @functools.lru_cache
326 332 def exit_counts(self) -> dict[TLineNo, int]:
Original file line number Diff line number Diff line change
@@ -151,6 +151,24 @@ def bar(self):
151 151 assert expected_arcs == parser.arcs()
152 152 assert expected_exits == parser.exit_counts()
153 153
154 +def test_nested_context_managers(self) -> None:
155 +# https://github.com/nedbat/coveragepy/issues/1876
156 +parser = self.parse_text("""\
157 + a = 1
158 + with suppress(ValueError):
159 + with suppress(ValueError):
160 + x = 4
161 + with suppress(ValueError):
162 + x = 6
163 + with suppress(ValueError):
164 + x = 8
165 + a = 9
166 + """)
167 +
168 +one_nine = set(range(1, 10))
169 +assert parser.statements == one_nine
170 +assert parser.exit_counts() == dict.fromkeys(one_nine, 1)
171 +
154 172 def test_module_docstrings(self) -> None:
155 173 parser = self.parse_text("""\
156 174 '''The docstring on line 1'''