Merge pull request #11160 from lesnek/ml/fix/warinings-recorder-pop

This commit is contained in:
Zac Hatfield-Dodds 2023-07-04 10:20:04 -07:00 committed by GitHub
commit d7dbadbffc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 3 deletions

View File

@ -263,6 +263,7 @@ Mickey Pashov
Mihai Capotă Mihai Capotă
Mike Hoyle (hoylemd) Mike Hoyle (hoylemd)
Mike Lundy Mike Lundy
Milan Lesnek
Miro Hrončok Miro Hrončok
Nathaniel Compton Nathaniel Compton
Nathaniel Waisbrot Nathaniel Waisbrot

View File

@ -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.

View File

@ -206,10 +206,21 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
return len(self._list) return len(self._list)
def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage": 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): for i, w in enumerate(self._list):
if issubclass(w.category, cls): if w.category == cls:
return self._list.pop(i) 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 __tracebackhide__ = True
raise AssertionError(f"{cls!r} not found in warning list") raise AssertionError(f"{cls!r} not found in warning list")

View File

@ -1,5 +1,7 @@
import warnings import warnings
from typing import List
from typing import Optional from typing import Optional
from typing import Type
import pytest import pytest
from _pytest.pytester import Pytester from _pytest.pytester import Pytester
@ -37,6 +39,47 @@ def test_recwarn_captures_deprecation_warning(recwarn: WarningsRecorder) -> None
assert recwarn.pop(DeprecationWarning) 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: class TestWarningsRecorderChecker:
def test_recording(self) -> None: def test_recording(self) -> None:
rec = WarningsRecorder(_ispytest=True) rec = WarningsRecorder(_ispytest=True)