-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WarningsRecorder.pop() improperly matches warning #10701
Comments
Yeah, it looks like this code never anticipated the possibility that a later warning might be a better match than the current warning. That said, we need to preserve the current behavior of matching if the warning was a subclass, so instead of the current for i, w in enumerate(self._list):
if issubclass(w.category, cls):
return self._list.pop(i) I propose we do something like # Match the first warning that is a subtype of `cls`, where there
# is *not* a later captured warning which is a subtype of this one.
matches = []
for i, w in enumerate(self._list):
if w.category == cls: # in this case we can exit early
return self._list.pop(i)
if issubclass(w.category, cls):
matches.append((i, w))
if not matches:
raise ... # see current code for details
(idx, best), *rest = matches
for i, w in rest:
if issubclass(w, best) and not issubclass(best, w):
idx, best = i, w
return self._list.pop(idx) and it would be awesome if you could open a PR with this, tests, a changelog entry, and of course adding yourself to the contributors list 🙏 |
Thank you for your comments @Zac-HD. Let me start by describing the challenge I am trying to work out in my code. I have a function that returns several warnings, and I want to check that for a certain set of arguments, all the expected warnings are emitted. This would mean checking that the proper warning classes have been captured and a matched portion of the warning message is also present. In an ideal world, I would think my test would probably look like: def test_warnings():
with pytest.warns(RWarning) as record: # RWarning is the base warning class for the library and all other warning inherit from it
my_function('a', 'b')
assert len(record) == 2
assert record.contains(RWarning, match_expr="Warning2") # this would be looking explicitly for RWarning, not its subclasses
assert record.contains(SWarning, match_expr="Warning1") I was thinking about this more and testing the code you wrote and feel that maybe a separate method would be a better solution. My ideas are:
def contains(self, cls, match_expr=None):
if match_expr:
match_re = re.compile(match_expr)
for w in self._list:
if w.category is cls and match_re.search(str(w.message)):
return True
else:
for w in self._list:
if w.category is cls:
return True
return False
m = list(record.match(RWarning, subclasses=False)) # do exact class matching
m = list(record.match(RWarning, subclasses=True)) # match with classes that are RWarnings or where RWarning is a base class Possible implentation: def match(self, cls, subclasses=False):
""" Match cls or cls subclasses """
if subclasses:
op = issubclass
else:
op = operator.is_
for i, w in enumerate(self._list):
if op(w.category, cls):
yield i
# Then .pop becomes
def pop(self, cls):
try:
i = next(self.match(cls, subclasses=True))
return self._list.pop(i)
except StopIteration:
__tracebackhide__ = True
raise AssertionError(f"{cls!r} not found in warning list") What are your thoughts, @Zac-HD? |
Seems like you might be better of with the stdlib Pytest helpers are nice, but sometimes it's worth using the rest of Python instead 😁 |
It does remind me of the plan to integrate dirty equals better into pytest, it would be nice if there was a way to spell the warning matches for the contains check |
Hello @Zac-HD , I want to start helping with open source, and I can see it is a |
When trying to pop a specific warning from a WarningsRecorder instance, the wrong warning is returned. I believe the issue is that pop uses issubclass
pytest/src/_pytest/recwarn.py
Line 210 in 3c15349
I believe the correct comparison should be:
Here is a minimum working example that triggers the buggy behavior:
The test output is
pytest 7.2.1 on archlinux.
virtual environment is a clean conda environment with only python and pytest (and their dependencies installed from conda-forge).
If this is indeed a bug, I'm happy to open a PR with my proposed solution.
The text was updated successfully, but these errors were encountered: