Skip to content

Commit

Permalink
Merge pull request #11160 from lesnek/ml/fix/warinings-recorder-pop
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD authored Jul 4, 2023
2 parents 6995257 + 7775e49 commit d7dbadb
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 3 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ Mickey Pashov
Mihai Capotă
Mike Hoyle (hoylemd)
Mike Lundy
Milan Lesnek
Miro Hrončok
Nathaniel Compton
Nathaniel Waisbrot
Expand Down
2 changes: 2 additions & 0 deletions changelog/10701.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:meth:`pytest.WarningsRecorder.pop` will return the most-closely-matched warning in the list,
rather than the first warning which is an instance of the requested type.
17 changes: 14 additions & 3 deletions src/_pytest/recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,21 @@ def __len__(self) -> int:
return len(self._list)

def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage":
"""Pop the first recorded warning, raise exception if not exists."""
"""Pop the first recorded warning which is an instance of ``cls``,
but not an instance of a child class of any other match.
Raises ``AssertionError`` if there is no match.
"""
best_idx: Optional[int] = None
for i, w in enumerate(self._list):
if issubclass(w.category, cls):
return self._list.pop(i)
if w.category == cls:
return self._list.pop(i) # exact match, stop looking
if issubclass(w.category, cls) and (
best_idx is None
or not issubclass(w.category, self._list[best_idx].category)
):
best_idx = i
if best_idx is not None:
return self._list.pop(best_idx)
__tracebackhide__ = True
raise AssertionError(f"{cls!r} not found in warning list")

Expand Down
43 changes: 43 additions & 0 deletions testing/test_recwarn.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import warnings
from typing import List
from typing import Optional
from typing import Type

import pytest
from _pytest.pytester import Pytester
Expand Down Expand Up @@ -37,6 +39,47 @@ def test_recwarn_captures_deprecation_warning(recwarn: WarningsRecorder) -> None
assert recwarn.pop(DeprecationWarning)


class TestSubclassWarningPop:
class ParentWarning(Warning):
pass

class ChildWarning(ParentWarning):
pass

class ChildOfChildWarning(ChildWarning):
pass

@staticmethod
def raise_warnings_from_list(_warnings: List[Type[Warning]]):
for warn in _warnings:
warnings.warn(f"Warning {warn().__repr__()}", warn)

def test_pop_finds_exact_match(self):
with pytest.warns((self.ParentWarning, self.ChildWarning)) as record:
self.raise_warnings_from_list(
[self.ChildWarning, self.ParentWarning, self.ChildOfChildWarning]
)

assert len(record) == 3
_warn = record.pop(self.ParentWarning)
assert _warn.category is self.ParentWarning

def test_pop_raises_if_no_match(self):
with pytest.raises(AssertionError):
with pytest.warns(self.ParentWarning) as record:
self.raise_warnings_from_list([self.ParentWarning])
record.pop(self.ChildOfChildWarning)

def test_pop_finds_best_inexact_match(self):
with pytest.warns(self.ParentWarning) as record:
self.raise_warnings_from_list(
[self.ChildOfChildWarning, self.ChildWarning, self.ChildOfChildWarning]
)

_warn = record.pop(self.ParentWarning)
assert _warn.category is self.ChildWarning


class TestWarningsRecorderChecker:
def test_recording(self) -> None:
rec = WarningsRecorder(_ispytest=True)
Expand Down

0 comments on commit d7dbadb

Please sign in to comment.