Skip to content

Commit d7dbadb

Browse files
authored
Merge pull request #11160 from lesnek/ml/fix/warinings-recorder-pop
2 parents 6995257 + 7775e49 commit d7dbadb

File tree

4 files changed

+60
-3
lines changed

4 files changed

+60
-3
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ Mickey Pashov
263263
Mihai Capotă
264264
Mike Hoyle (hoylemd)
265265
Mike Lundy
266+
Milan Lesnek
266267
Miro Hrončok
267268
Nathaniel Compton
268269
Nathaniel Waisbrot

changelog/10701.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:meth:`pytest.WarningsRecorder.pop` will return the most-closely-matched warning in the list,
2+
rather than the first warning which is an instance of the requested type.

src/_pytest/recwarn.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,21 @@ def __len__(self) -> int:
206206
return len(self._list)
207207

208208
def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage":
209-
"""Pop the first recorded warning, raise exception if not exists."""
209+
"""Pop the first recorded warning which is an instance of ``cls``,
210+
but not an instance of a child class of any other match.
211+
Raises ``AssertionError`` if there is no match.
212+
"""
213+
best_idx: Optional[int] = None
210214
for i, w in enumerate(self._list):
211-
if issubclass(w.category, cls):
212-
return self._list.pop(i)
215+
if w.category == cls:
216+
return self._list.pop(i) # exact match, stop looking
217+
if issubclass(w.category, cls) and (
218+
best_idx is None
219+
or not issubclass(w.category, self._list[best_idx].category)
220+
):
221+
best_idx = i
222+
if best_idx is not None:
223+
return self._list.pop(best_idx)
213224
__tracebackhide__ = True
214225
raise AssertionError(f"{cls!r} not found in warning list")
215226

testing/test_recwarn.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import warnings
2+
from typing import List
23
from typing import Optional
4+
from typing import Type
35

46
import pytest
57
from _pytest.pytester import Pytester
@@ -37,6 +39,47 @@ def test_recwarn_captures_deprecation_warning(recwarn: WarningsRecorder) -> None
3739
assert recwarn.pop(DeprecationWarning)
3840

3941

42+
class TestSubclassWarningPop:
43+
class ParentWarning(Warning):
44+
pass
45+
46+
class ChildWarning(ParentWarning):
47+
pass
48+
49+
class ChildOfChildWarning(ChildWarning):
50+
pass
51+
52+
@staticmethod
53+
def raise_warnings_from_list(_warnings: List[Type[Warning]]):
54+
for warn in _warnings:
55+
warnings.warn(f"Warning {warn().__repr__()}", warn)
56+
57+
def test_pop_finds_exact_match(self):
58+
with pytest.warns((self.ParentWarning, self.ChildWarning)) as record:
59+
self.raise_warnings_from_list(
60+
[self.ChildWarning, self.ParentWarning, self.ChildOfChildWarning]
61+
)
62+
63+
assert len(record) == 3
64+
_warn = record.pop(self.ParentWarning)
65+
assert _warn.category is self.ParentWarning
66+
67+
def test_pop_raises_if_no_match(self):
68+
with pytest.raises(AssertionError):
69+
with pytest.warns(self.ParentWarning) as record:
70+
self.raise_warnings_from_list([self.ParentWarning])
71+
record.pop(self.ChildOfChildWarning)
72+
73+
def test_pop_finds_best_inexact_match(self):
74+
with pytest.warns(self.ParentWarning) as record:
75+
self.raise_warnings_from_list(
76+
[self.ChildOfChildWarning, self.ChildWarning, self.ChildOfChildWarning]
77+
)
78+
79+
_warn = record.pop(self.ParentWarning)
80+
assert _warn.category is self.ChildWarning
81+
82+
4083
class TestWarningsRecorderChecker:
4184
def test_recording(self) -> None:
4285
rec = WarningsRecorder(_ispytest=True)

0 commit comments

Comments
 (0)