WarningsRecorder.pop() (and pytest.warns(...).pop() / recwarn.pop()) returns the last matching warning instead of the first one when the recorded warnings have categories that are unrelated to each other.
import warnings
def test_pop(recwarn):
warnings.warn("first", UserWarning)
warnings.warn("second", RuntimeWarning)
warnings.warn("third", DeprecationWarning)
assert str(recwarn.pop().message) == "first"
E AssertionError: assert 'third' == 'first'
The docstring says pop returns "the first recorded warning which is an instance of cls, but not an instance of a child class of any other match". None of the three categories is a subclass of another, so the first one should come back. That was also the behaviour before 8.0.
The cause is the "best inexact match" loop added in #11160 (the fix for #10701):
if issubclass(w.category, cls) and (
best_idx is None
or not issubclass(w.category, self._list[best_idx].category)
):
best_idx = i
not issubclass(new, best) is true both when new is more general than best and when the two are unrelated siblings. So every sibling replaces the current best, and the loop ends up on the last one.
The existing tests in TestSubclassWarningPop only use a single parent/child chain, so they don't catch this. test_recording pops a UserWarning and a DeprecationWarning, but both have the message "hello", so it passes whichever one comes back.
Reproduced on main (pytest 9.2.0.dev345+g872117358), Python 3.13.5, Windows 11. The loop was introduced by #11160, so the bug is present since 8.0.0; in 7.4.x pop() returned the first match. I have a fix with regression tests ready and will open a PR.
WarningsRecorder.pop()(andpytest.warns(...).pop()/recwarn.pop()) returns the last matching warning instead of the first one when the recorded warnings have categories that are unrelated to each other.The docstring says
popreturns "the first recorded warning which is an instance ofcls, but not an instance of a child class of any other match". None of the three categories is a subclass of another, so the first one should come back. That was also the behaviour before 8.0.The cause is the "best inexact match" loop added in #11160 (the fix for #10701):
not issubclass(new, best)is true both whennewis more general thanbestand when the two are unrelated siblings. So every sibling replaces the current best, and the loop ends up on the last one.The existing tests in
TestSubclassWarningPoponly use a single parent/child chain, so they don't catch this.test_recordingpops aUserWarningand aDeprecationWarning, but both have the message"hello", so it passes whichever one comes back.Reproduced on main (pytest 9.2.0.dev345+g872117358), Python 3.13.5, Windows 11. The loop was introduced by #11160, so the bug is present since 8.0.0; in 7.4.x
pop()returned the first match. I have a fix with regression tests ready and will open a PR.